Merge pull request #2 from GetStream/fix/loading-screen

feat: Added native and local splash screens
This commit is contained in:
Salvatore Giordano
2021-02-16 17:22:42 +01:00
committed by GitHub
21 changed files with 387 additions and 151 deletions
+1
View File
@@ -2,6 +2,7 @@
.atom/ .atom/
.idea/ .idea/
.vscode/ .vscode/
**/.fvm/
.packages .packages
.pub/ .pub/
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" /> <item android:drawable="@color/splash_color"/>
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list> </layer-list>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_color">#005fff</color>
</resources>
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
15590bfc713ca5ccd71d79a75eb28980 c3757524e06ced55f3d1b83411b9e99c
@@ -1,2 +1,3 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"
@@ -1,2 +1,3 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"
@@ -309,10 +309,11 @@
); );
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/Starscream-framework/Starscream.framework",
"${BUILT_PRODUCTS_DIR}/StreamChatClient-framework/StreamChatClient.framework",
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework", "${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework", "${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
"${PODS_ROOT}/../Flutter/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework", "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/esys_flutter_share/esys_flutter_share.framework", "${BUILT_PRODUCTS_DIR}/esys_flutter_share/esys_flutter_share.framework",
@@ -336,10 +337,11 @@
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Starscream.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/StreamChatClient.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/esys_flutter_share.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/esys_flutter_share.framework",
@@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "self:">
</FileRef> </FileRef>
</Workspace> </Workspace>
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

@@ -1,23 +1,23 @@
{ {
"images" : [ "images" : [
{ {
"idiom" : "universal",
"filename" : "LaunchImage.png", "filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "universal",
"filename" : "[email protected]", "filename" : "[email protected]",
"idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "universal",
"filename" : "[email protected]", "filename" : "[email protected]",
"idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }
], ],
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 68 B

@@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
</imageView> <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews> </subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/> <constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/> <constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints> </constraints>
</view> </view>
</viewController> </viewController>
@@ -33,5 +39,6 @@
</scenes> </scenes>
<resources> <resources>
<image name="LaunchImage" width="168" height="185"/> <image name="LaunchImage" width="168" height="185"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources> </resources>
</document> </document>
+2
View File
@@ -60,5 +60,7 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<true/> <true/>
<key>UIStatusBarHidden</key>
<false/>
</dict> </dict>
</plist> </plist>
@@ -33,7 +33,6 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
resizeToAvoidBottomPadding: false,
appBar: AppBar( appBar: AppBar(
backgroundColor: StreamChatTheme.of(context).colorTheme.white, backgroundColor: StreamChatTheme.of(context).colorTheme.white,
elevation: 1, elevation: 1,
+228 -21
View File
@@ -6,8 +6,10 @@ import 'package:example/group_info_screen.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:lottie/lottie.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
@@ -18,12 +20,30 @@ import 'routes/routes.dart';
import 'search_text_field.dart'; import 'search_text_field.dart';
final chatPersistentClient = StreamChatPersistenceClient( final chatPersistentClient = StreamChatPersistenceClient(
logLevel: Level.INFO, logLevel: Level.SEVERE,
connectionMode: ConnectionMode.background, connectionMode: ConnectionMode.background,
); );
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
InitData _initData;
bool _animCompleted = false;
Animation<double> _animation, _scaleAnimation;
AnimationController _animationController, _scaleAnimationController;
Animation<Color> _colorAnimation;
int timeOfStartMs;
Future<InitData> _initConnection() async {
final secureStorage = FlutterSecureStorage(); final secureStorage = FlutterSecureStorage();
final apiKey = await secureStorage.read(key: kStreamApiKey); final apiKey = await secureStorage.read(key: kStreamApiKey);
@@ -31,7 +51,7 @@ void main() async {
final client = StreamChatClient( final client = StreamChatClient(
apiKey ?? kDefaultStreamApiKey, apiKey ?? kDefaultStreamApiKey,
logLevel: Level.INFO, logLevel: Level.SEVERE,
)..chatPersistenceClient = chatPersistentClient; )..chatPersistenceClient = chatPersistentClient;
if (userId != null) { if (userId != null) {
@@ -42,33 +62,165 @@ void main() async {
); );
} }
runApp(MyApp(client)); var prefs = await StreamingSharedPreferences.instance;
return InitData(client, prefs);
} }
class MyApp extends StatelessWidget { void _createAnimations() {
final StreamChatClient client; _scaleAnimationController = AnimationController(
vsync: this,
value: 0,
duration: Duration(
milliseconds: 500,
),
);
_scaleAnimation = Tween(
begin: 1.0,
end: 1.5,
).animate(CurvedAnimation(
parent: _scaleAnimationController,
curve: Curves.easeInOutBack,
));
MyApp(this.client); _animationController = AnimationController(
vsync: this,
duration: Duration(
milliseconds: 1000,
),
);
_animation = Tween(
begin: 0.0,
end: 1000.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
_colorAnimation = ColorTween(
begin: Color(0xff005FFF),
end: Color(0xff005FFF),
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
_colorAnimation = ColorTween(
begin: Color(0xff005FFF),
end: Colors.transparent,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
}
@override
void initState() {
timeOfStartMs = DateTime.now().millisecondsSinceEpoch;
_createAnimations();
_initConnection().then(
(initData) {
setState(() {
_initData = initData;
});
var now = DateTime.now().millisecondsSinceEpoch;
if (now - timeOfStartMs > 1500) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
_scaleAnimationController.forward().whenComplete(() {
_animationController.forward();
});
});
} else {
Future.delayed(Duration(milliseconds: 1500)).then((value) {
_scaleAnimationController.forward().whenComplete(() {
_animationController.forward();
});
});
}
},
);
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
_animCompleted = true;
});
}
});
super.initState();
}
Widget _buildAnimation() {
return MaterialApp(
home: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, _) {
return Transform.scale(
scale: _scaleAnimation.value,
child: AnimatedBuilder(
animation: _colorAnimation,
builder: (context, snapshot) {
return Container(
alignment: Alignment.center,
constraints: BoxConstraints.expand(),
color: _colorAnimation == null
? Color(0xff005FFF)
: _colorAnimation.value,
child: !_animationController.isAnimating
? Lottie.asset(
'assets/floating_boat.json',
alignment: Alignment.center,
)
: SizedBox(),
);
}),
);
},
),
AnimatedBuilder(
animation: _animation,
builder: (context, snapshot) {
return Transform.scale(
scale: _animation.value,
child: Container(
width: 1.0,
height: 1.0,
decoration: BoxDecoration(
color: Colors.white
.withOpacity(1 - _animationController.value),
shape: BoxShape.circle,
),
),
);
},
),
],
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder<StreamingSharedPreferences>( return Stack(
future: StreamingSharedPreferences.instance, alignment: Alignment.center,
builder: (context, snapshot) { children: [
if (!snapshot.hasData) { if (_initData != null)
return SizedBox(); PreferenceBuilder<int>(
} preference: _initData.preferences.getInt(
return PreferenceBuilder<int>(
preference: snapshot.data.getInt(
'theme', 'theme',
defaultValue: 0, defaultValue: 0,
), ),
builder: (context, snapshot) => MaterialApp( builder: (context, snapshot) => MaterialApp(
builder: (context, child) { builder: (context, child) {
return StreamChat( return StreamChat(
client: client, client: _initData.client,
onBackgroundEventReceived: (e) => onBackgroundEventReceived: (e) =>
showLocalNotification(e, client.state.user.id), showLocalNotification(e, _initData.client.state.user.id),
child: Builder( child: Builder(
builder: (context) => AnnotatedRegion<SystemUiOverlayStyle>( builder: (context) => AnnotatedRegion<SystemUiOverlayStyle>(
child: child, child: child,
@@ -84,7 +236,6 @@ class MyApp extends StatelessWidget {
), ),
); );
}, },
debugShowCheckedModeBanner: false,
theme: ThemeData.light(), theme: ThemeData.light(),
darkTheme: ThemeData.dark(), darkTheme: ThemeData.dark(),
themeMode: { themeMode: {
@@ -93,11 +244,13 @@ class MyApp extends StatelessWidget {
1: ThemeMode.light, 1: ThemeMode.light,
}[snapshot], }[snapshot],
onGenerateRoute: AppRoutes.generateRoute, onGenerateRoute: AppRoutes.generateRoute,
initialRoute: initialRoute: _initData.client.state.user == null
client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, ? Routes.CHOOSE_USER
: Routes.HOME,
), ),
); ),
}, if (!_animCompleted) _buildAnimation(),
],
); );
} }
} }
@@ -833,3 +986,57 @@ class _ThreadPageState extends State<ThreadPage> {
); );
} }
} }
class InitData {
final StreamChatClient client;
final StreamingSharedPreferences preferences;
InitData(this.client, this.preferences);
}
class HolePainter extends CustomPainter {
HolePainter({
@required this.color,
@required this.holeSize,
});
Color color;
double holeSize;
@override
void paint(Canvas canvas, Size size) {
double radius = holeSize / 2;
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
Rect outerCircleRect = Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2), radius: radius);
Rect innerCircleRect = Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2), radius: radius / 2);
Path transparentHole = Path.combine(
PathOperation.difference,
Path()..addRect(rect),
Path()
..addOval(outerCircleRect)
..close(),
);
Path halfTransparentRing = Path.combine(
PathOperation.difference,
Path()
..addOval(outerCircleRect)
..close(),
Path()
..addOval(innerCircleRect)
..close(),
);
canvas.drawPath(transparentHole, Paint()..color = color);
canvas.drawPath(
halfTransparentRing, Paint()..color = color.withOpacity(0.5));
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ class AppRoutes {
return MaterialPageRoute( return MaterialPageRoute(
settings: const RouteSettings(name: Routes.APP), settings: const RouteSettings(name: Routes.APP),
builder: (_) { builder: (_) {
return MyApp(args); return MyApp();
}); });
case Routes.HOME: case Routes.HOME:
return MaterialPageRoute( return MaterialPageRoute(
+1 -4
View File
@@ -17,12 +17,9 @@ dependencies:
yaml: ^2.2.1 yaml: ^2.2.1
uuid: ^2.2.2 uuid: ^2.2.2
streaming_shared_preferences: ^1.0.2 streaming_shared_preferences: ^1.0.2
lottie: ^0.7.0+1
dev_dependencies: dev_dependencies:
flutter_test:
sdk: flutter
flutter_driver:
sdk: flutter
flutter_launcher_icons: ^0.8.1 flutter_launcher_icons: ^0.8.1
test: any test: any