refactor home page

This commit is contained in:
Salvatore Giordano
2021-06-22 12:52:11 +02:00
parent 3638e36471
commit 6acaf96466
12 changed files with 647 additions and 650 deletions
@@ -1,3 +1,4 @@
import 'package:example/home_page.dart';
import 'package:example/routes/routes.dart';
import 'package:example/stream_version.dart';
import 'package:flutter/material.dart';
@@ -306,7 +307,6 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
key: kStreamToken,
value: userToken,
);
client.closeConnection();
} catch (e) {
var errorText = 'Error connecting, retry';
if (e is Map) {
@@ -317,15 +317,14 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
_apiKeyError = errorText.toUpperCase();
});
loading = false;
client.closeConnection();
return;
}
loading = false;
await Navigator.pushNamedAndRemoveUntil(
context,
Routes.APP,
ModalRoute.withName(Routes.APP),
arguments: client,
Routes.HOME,
ModalRoute.withName(Routes.HOME),
arguments: HomePageArgs(client),
);
}
},
@@ -0,0 +1,199 @@
import 'dart:async';
import 'package:example/routes/routes.dart';
import 'package:example/search_text_field.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'channel_page.dart';
import 'chat_info_screen.dart';
import 'group_info_screen.dart';
class ChannelList extends StatefulWidget {
@override
_ChannelList createState() => _ChannelList();
}
class _ChannelList extends State<ChannelList> {
TextEditingController? _controller;
String _channelQuery = '';
bool _isSearchActive = false;
Timer? _debounce;
void _channelQueryListener() {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 350), () {
if (mounted) {
setState(() {
_channelQuery = _controller!.text;
_isSearchActive = _channelQuery.isNotEmpty;
});
}
});
}
@override
void initState() {
super.initState();
_controller = TextEditingController()..addListener(_channelQueryListener);
}
@override
void dispose() {
_controller?.removeListener(_channelQueryListener);
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final user = StreamChat.of(context).user;
return WillPopScope(
onWillPop: () async {
if (_isSearchActive) {
_controller!.clear();
setState(() => _isSearchActive = false);
return false;
}
return true;
},
child: ChannelsBloc(
child: MessageSearchBloc(
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (_, __) => [
SliverToBoxAdapter(
child: SearchTextField(
controller: _controller,
showCloseButton: _isSearchActive,
),
),
],
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: _isSearchActive
? MessageSearchListView(
showErrorTile: true,
messageQuery: _channelQuery,
filters: Filter.in_('members', [user!.id]),
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
pullToRefresh: false,
paginationParams: PaginationParams(limit: 20),
emptyBuilder: (_) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search(
size: 96,
color: Colors.grey,
),
),
Text(
'No results...',
),
],
),
),
),
);
},
);
},
onItemTap: (messageResponse) async {
FocusScope.of(context).requestFocus(FocusNode());
final client = StreamChat.of(context).client;
final message = messageResponse.message;
final channel = client.channel(
messageResponse.channel!.type,
id: messageResponse.channel!.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
)
: ChannelListView(
onStartChatPressed: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
swipeToAction: true,
filter: Filter.in_('members', [user!.id]),
presence: true,
pagination: PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
onViewInfoTap: (channel) {
Navigator.pop(context);
if (channel.memberCount == 2 && channel.isDistinct) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: ChatInfoScreen(
messageTheme: StreamChatTheme.of(context)
.ownMessageTheme,
user: channel.state!.members
.where((m) =>
m.userId !=
channel.client.state.user!.id)
.first
.user,
),
),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: GroupInfoScreen(
messageTheme: StreamChatTheme.of(context)
.ownMessageTheme,
),
),
),
);
}
},
),
),
),
),
),
),
);
}
}
+249 -167
View File
@@ -1,195 +1,277 @@
import 'dart:async';
import 'package:example/routes/routes.dart';
import 'package:example/search_text_field.dart';
import 'package:example/user_mentions_page.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
import 'channel_page.dart';
import 'chat_info_screen.dart';
import 'group_info_screen.dart';
import 'channel_list.dart';
class ChannelListPage extends StatefulWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override
_ChannelListPageState createState() => _ChannelListPageState();
}
class _ChannelListPageState extends State<ChannelListPage> {
TextEditingController? _controller;
int _currentIndex = 0;
String _channelQuery = '';
bool _isSelected(int index) => _currentIndex == index;
bool _isSearchActive = false;
Timer? _debounce;
void _channelQueryListener() {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 350), () {
if (mounted) {
setState(() {
_channelQuery = _controller!.text;
_isSearchActive = _channelQuery.isNotEmpty;
});
}
});
}
@override
void initState() {
super.initState();
_controller = TextEditingController()..addListener(_channelQueryListener);
}
@override
void dispose() {
_controller?.removeListener(_channelQueryListener);
_controller?.dispose();
super.dispose();
List<BottomNavigationBarItem> get _navBarItems {
return <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Stack(
clipBehavior: Clip.none,
children: [
StreamSvgIcon.message(
color: _isSelected(0)
? StreamChatTheme.of(context).colorTheme.black
: Colors.grey,
),
Positioned(
top: -3,
right: -16,
child: UnreadIndicator(),
),
],
),
label: 'Chats',
),
BottomNavigationBarItem(
icon: Stack(
clipBehavior: Clip.none,
children: [
StreamSvgIcon.mentions(
color: _isSelected(1)
? StreamChatTheme.of(context).colorTheme.black
: Colors.grey,
),
],
),
label: 'Mentions',
),
];
}
@override
Widget build(BuildContext context) {
final user = StreamChat.of(context).user;
return WillPopScope(
onWillPop: () async {
if (_isSearchActive) {
_controller!.clear();
setState(() => _isSearchActive = false);
return false;
if (user == null) {
return Offstage();
}
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
appBar: ChannelListHeader(
onNewChatButtonTap: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
preNavigationCallback: () {
FocusScope.of(context).requestFocus(FocusNode());
},
),
drawer: LeftDrawer(
user: user,
),
drawerEdgeDragWidth: 50,
bottomNavigationBar: BottomNavigationBar(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
currentIndex: _currentIndex,
items: _navBarItems,
selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold,
unselectedLabelStyle:
StreamChatTheme.of(context).textTheme.footnoteBold,
type: BottomNavigationBarType.fixed,
selectedItemColor: StreamChatTheme.of(context).colorTheme.black,
unselectedItemColor: Colors.grey,
onTap: (index) {
setState(() => _currentIndex = index);
},
),
body: IndexedStack(
index: _currentIndex,
children: [
ChannelList(),
UserMentionsPage(),
],
),
);
}
StreamSubscription<int>? badgeListener;
@override
void initState() {
if (!kIsWeb) {
badgeListener = StreamChat.of(context)
.client
.state
.totalUnreadCountStream
.listen((count) {
if (count > 0) {
FlutterAppBadger.updateBadgeCount(count);
} else {
FlutterAppBadger.removeBadge();
}
return true;
},
child: ChannelsBloc(
child: MessageSearchBloc(
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (_, __) => [
SliverToBoxAdapter(
child: SearchTextField(
controller: _controller,
showCloseButton: _isSearchActive,
),
),
],
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: _isSearchActive
? MessageSearchListView(
showErrorTile: true,
messageQuery: _channelQuery,
filters: Filter.in_('members', [user!.id]),
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
});
}
super.initState();
}
@override
void dispose() {
badgeListener?.cancel();
super.dispose();
}
}
class LeftDrawer extends StatelessWidget {
const LeftDrawer({
Key? key,
required this.user,
}) : super(key: key);
final User user;
@override
Widget build(BuildContext context) {
return Drawer(
child: Container(
color: StreamChatTheme.of(context).colorTheme.white,
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).viewPadding.top + 8,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
bottom: 20.0,
left: 8,
),
child: Row(
children: [
UserAvatar(
user: user,
showOnlineStatus: false,
constraints: BoxConstraints.tight(Size.fromRadius(20)),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
user.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
],
pullToRefresh: false,
paginationParams: PaginationParams(limit: 20),
emptyBuilder: (_) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search(
size: 96,
color: Colors.grey,
),
),
Text(
'No results...',
),
],
),
),
),
);
},
);
},
onItemTap: (messageResponse) async {
FocusScope.of(context).requestFocus(FocusNode());
final client = StreamChat.of(context).client;
final message = messageResponse.message;
final channel = client.channel(
messageResponse.channel!.type,
id: messageResponse.channel!.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
)
: ChannelListView(
onStartChatPressed: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
swipeToAction: true,
filter: Filter.in_('members', [user!.id]),
presence: true,
pagination: PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
onViewInfoTap: (channel) {
Navigator.pop(context);
if (channel.memberCount == 2 && channel.isDistinct) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: ChatInfoScreen(
messageTheme: StreamChatTheme.of(context)
.ownMessageTheme,
user: channel.state!.members
.where((m) =>
m.userId !=
channel.client.state.user!.id)
.first
.user,
),
),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: GroupInfoScreen(
messageTheme: StreamChatTheme.of(context)
.ownMessageTheme,
),
),
),
);
}
),
],
),
),
ListTile(
leading: StreamSvgIcon.penWrite(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
onTap: () {
Navigator.popAndPushNamed(
context,
Routes.NEW_CHAT,
);
},
title: Text(
'New direct message',
style: TextStyle(
fontSize: 14.5,
),
),
),
ListTile(
leading: StreamSvgIcon.contacts(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
onTap: () {
Navigator.popAndPushNamed(
context,
Routes.NEW_GROUP_CHAT,
);
},
title: Text(
'New group',
style: TextStyle(
fontSize: 14.5,
),
),
),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
child: ListTile(
onTap: () async {
Navigator.pop(context);
if (!kIsWeb) {
final secureStorage = FlutterSecureStorage();
await secureStorage.deleteAll();
}
final client = StreamChat.of(context).client;
client.disconnectUser();
await client.dispose();
await Navigator.of(
context,
rootNavigator: true,
).pushNamedAndRemoveUntil(
Routes.CHOOSE_USER,
ModalRoute.withName(Routes.CHOOSE_USER),
);
},
leading: StreamSvgIcon.user(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
title: Text(
'Sign out',
style: TextStyle(
fontSize: 14.5,
),
),
trailing: IconButton(
icon: StreamSvgIcon.iconMoon(
size: 24,
),
color: StreamChatTheme.of(context).colorTheme.grey,
onPressed: () async {
final sp = await StreamingSharedPreferences.instance;
sp.setInt(
'theme',
Theme.of(context).brightness == Brightness.dark
? 1
: -1,
);
},
),
),
),
),
),
],
),
),
),
@@ -71,21 +71,7 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
fontSize: 16.0,
),
),
leading: Center(
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
width: 24.0,
height: 24.0,
child: StreamSvgIcon.left(
color: StreamChatTheme.of(context).colorTheme.black,
size: 24.0,
),
),
),
),
leading: StreamBackButton(),
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
),
body: _buildMediaGrid(),
@@ -1,4 +1,5 @@
import 'package:example/app_config.dart';
import 'package:example/home_page.dart';
import 'package:example/stream_version.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -6,6 +7,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'main.dart';
import 'routes/routes.dart';
const kStreamApiKey = 'STREAM_API_KEY';
@@ -89,8 +91,11 @@ class ChooseUserPage extends StatelessWidget {
),
);
final client = StreamChat.of(context).client;
// client.apiKey = kDefaultStreamApiKey;
final client = StreamChatClient(
kDefaultStreamApiKey,
logLevel: Level.INFO,
)..chatPersistenceClient = chatPersistentClient;
await client.connectUser(
user,
token,
@@ -115,6 +120,7 @@ class ChooseUserPage extends StatelessWidget {
context,
Routes.HOME,
ModalRoute.withName(Routes.HOME),
arguments: HomePageArgs(client),
);
},
leading: UserAvatar(
+13 -223
View File
@@ -1,240 +1,30 @@
import 'package:example/routes/app_routes.dart';
import 'package:example/routes/routes.dart';
import 'package:example/user_mentions_page.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
import 'channel_list_page.dart';
class HomePageArgs {
final StreamChatClient chatClient;
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
HomePageArgs(this.chatClient);
}
class _HomePageState extends State<HomePage> {
int _currentIndex = 0;
bool _isSelected(int index) => _currentIndex == index;
List<BottomNavigationBarItem> get _navBarItems {
return <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Stack(
clipBehavior: Clip.none,
children: [
StreamSvgIcon.message(
color: _isSelected(0)
? StreamChatTheme.of(context).colorTheme.black
: Colors.grey,
),
Positioned(
top: -3,
right: -16,
child: UnreadIndicator(),
),
],
),
label: 'Chats',
),
BottomNavigationBarItem(
icon: Stack(
clipBehavior: Clip.none,
children: [
StreamSvgIcon.mentions(
color: _isSelected(1)
? StreamChatTheme.of(context).colorTheme.black
: Colors.grey,
),
],
),
label: 'Mentions',
),
];
}
@override
Widget build(BuildContext context) {
final user = StreamChat.of(context).user!;
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
appBar: ChannelListHeader(
onNewChatButtonTap: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
preNavigationCallback: () {
FocusScope.of(context).requestFocus(FocusNode());
},
),
drawer: LeftDrawer(
user: user,
),
drawerEdgeDragWidth: 50,
bottomNavigationBar: BottomNavigationBar(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
currentIndex: _currentIndex,
items: _navBarItems,
selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold,
unselectedLabelStyle:
StreamChatTheme.of(context).textTheme.footnoteBold,
type: BottomNavigationBarType.fixed,
selectedItemColor: StreamChatTheme.of(context).colorTheme.black,
unselectedItemColor: Colors.grey,
onTap: (index) {
setState(() => _currentIndex = index);
},
),
body: IndexedStack(
index: _currentIndex,
children: [
ChannelListPage(),
UserMentionsPage(),
],
),
);
}
}
class LeftDrawer extends StatelessWidget {
const LeftDrawer({
class HomePage extends StatelessWidget {
HomePage({
Key? key,
required this.user,
required this.chatClient,
}) : super(key: key);
final User user;
final StreamChatClient chatClient;
@override
Widget build(BuildContext context) {
return Drawer(
child: Container(
color: StreamChatTheme.of(context).colorTheme.white,
child: SafeArea(
child: Padding(
padding: EdgeInsets.only(
top: MediaQuery.of(context).viewPadding.top + 8,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
bottom: 20.0,
left: 8,
),
child: Row(
children: [
UserAvatar(
user: user,
showOnlineStatus: false,
constraints: BoxConstraints.tight(Size.fromRadius(20)),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
user.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
ListTile(
leading: StreamSvgIcon.penWrite(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
onTap: () {
Navigator.popAndPushNamed(
context,
Routes.NEW_CHAT,
);
},
title: Text(
'New direct message',
style: TextStyle(
fontSize: 14.5,
),
),
),
ListTile(
leading: StreamSvgIcon.contacts(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
onTap: () {
Navigator.popAndPushNamed(
context,
Routes.NEW_GROUP_CHAT,
);
},
title: Text(
'New group',
style: TextStyle(
fontSize: 14.5,
),
),
),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
child: ListTile(
onTap: () async {
Navigator.pop(context);
if (!kIsWeb) {
final secureStorage = FlutterSecureStorage();
await secureStorage.deleteAll();
}
StreamChat.of(context).client.disconnectUser();
await Navigator.pushNamedAndRemoveUntil(
context,
Routes.APP,
ModalRoute.withName(Routes.APP),
);
},
leading: StreamSvgIcon.user(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
title: Text(
'Sign out',
style: TextStyle(
fontSize: 14.5,
),
),
trailing: IconButton(
icon: StreamSvgIcon.iconMoon(
size: 24,
),
color: StreamChatTheme.of(context).colorTheme.grey,
onPressed: () async {
final sp = await StreamingSharedPreferences.instance;
sp.setInt(
'theme',
Theme.of(context).brightness == Brightness.dark
? 1
: -1,
);
},
),
),
),
),
],
),
),
),
return StreamChat(
client: chatClient,
child: Navigator(
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: Routes.CHANNEL_LIST_PAGE,
),
);
}
+34 -206
View File
@@ -1,20 +1,18 @@
import 'dart:async';
import 'package:example/choose_user_page.dart';
import 'package:example/home_page.dart';
import 'package:example/splash_screen.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app_badger/flutter_app_badger.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_persistence/stream_chat_persistence.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
import 'app_config.dart';
import 'notifications_service.dart';
import 'routes/app_routes.dart';
import 'routes/routes.dart';
@@ -24,8 +22,6 @@ final chatPersistentClient = StreamChatPersistenceClient(
);
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
@@ -34,13 +30,9 @@ class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
class _MyAppState extends State<MyApp>
with SplashScreenStateMixin, TickerProviderStateMixin {
InitData? _initData;
bool _animCompleted = false;
Animation<double>? _animation, _scaleAnimation;
AnimationController? _animationController, _scaleAnimationController;
Animation<Color?>? _colorAnimation;
late int timeOfStartMs;
Future<InitData> _initConnection() async {
String? apiKey, userId, token;
@@ -69,56 +61,9 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
return InitData(client, prefs);
}
void _createAnimations() {
_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,
));
_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();
final timeOfStartMs = DateTime.now().millisecondsSinceEpoch;
_initConnection().then(
(initData) {
@@ -126,97 +71,23 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
_initData = initData;
});
var now = DateTime.now().millisecondsSinceEpoch;
final now = DateTime.now().millisecondsSinceEpoch;
if (now - timeOfStartMs > 1500) {
SchedulerBinding.instance!.addPostFrameCallback((timeStamp) {
_scaleAnimationController?.forward().whenComplete(() {
_animationController?.forward();
});
forwardAnimations();
});
} else {
Future.delayed(Duration(milliseconds: 1500)).then((value) {
_scaleAnimationController?.forward().whenComplete(() {
_animationController?.forward();
});
});
}
if (!kIsWeb) {
_initData!.client.state.totalUnreadCountStream.listen((count) {
if (count > 0) {
FlutterAppBadger.updateBadgeCount(count);
} else {
FlutterAppBadger.removeBadge();
}
forwardAnimations();
});
}
},
);
_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
Widget build(BuildContext context) {
return Stack(
@@ -229,27 +100,6 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
defaultValue: 0,
),
builder: (context, snapshot) => MaterialApp(
builder: (context, child) {
return StreamChat(
backgroundKeepAlive: Duration(seconds: 5),
client: _initData!.client,
onBackgroundEventReceived: (e) => showLocalNotification(
e, _initData!.client.state.user!.id),
child: Builder(
builder: (context) => AnnotatedRegion<SystemUiOverlayStyle>(
child: child!,
value: SystemUiOverlayStyle(
systemNavigationBarColor:
StreamChatTheme.of(context).colorTheme.white,
systemNavigationBarIconBrightness:
Theme.of(context).brightness == Brightness.dark
? Brightness.light
: Brightness.dark,
),
),
),
);
},
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: {
@@ -257,13 +107,38 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
0: ThemeMode.system,
1: ThemeMode.light,
}[snapshot],
builder: (context, child) => StreamChatTheme(
data: StreamChatThemeData(
brightness: Theme.of(context).brightness,
),
child: child!,
),
onGenerateRoute: AppRoutes.generateRoute,
onGenerateInitialRoutes: (initialRouteName) {
if (initialRouteName == Routes.HOME) {
return [
AppRoutes.generateRoute(
RouteSettings(
name: Routes.HOME,
arguments: HomePageArgs(_initData!.client),
),
)!
];
}
return [
AppRoutes.generateRoute(
RouteSettings(
name: Routes.CHOOSE_USER,
),
)!
];
},
initialRoute: _initData!.client.state.user == null
? Routes.CHOOSE_USER
: Routes.HOME,
),
),
if (!_animCompleted) _buildAnimation(),
if (!animationCompleted) buildAnimation(),
],
);
}
@@ -275,50 +150,3 @@ class InitData {
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;
}
}
@@ -70,21 +70,7 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
fontSize: 16.0,
),
),
leading: Center(
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
width: 24.0,
height: 24.0,
child: StreamSvgIcon.left(
color: StreamChatTheme.of(context).colorTheme.black,
size: 24.0,
),
),
),
),
leading: StreamBackButton(),
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
),
body: _buildMediaGrid(),
@@ -1,3 +1,4 @@
import 'package:example/channel_list_page.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -28,7 +29,10 @@ class AppRoutes {
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.HOME),
builder: (_) {
return HomePage();
final homePageArgs = args as HomePageArgs;
return HomePage(
chatClient: homePageArgs.chatClient,
);
});
case Routes.CHOOSE_USER:
return MaterialPageRoute(
@@ -92,6 +96,12 @@ class AppRoutes {
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
);
});
case Routes.CHANNEL_LIST_PAGE:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.CHANNEL_LIST_PAGE),
builder: (context) {
return ChannelListPage();
});
// Default case, should not reach here.
default:
return null;
@@ -10,4 +10,5 @@ class Routes {
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
static const String CHAT_INFO_SCREEN = '/chat_info_screen';
static const String GROUP_INFO_SCREEN = '/group_info_screen';
static const String CHANNEL_LIST_PAGE = '/channel_list_page';
}
@@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:lottie/lottie.dart';
mixin SplashScreenStateMixin<T extends StatefulWidget> on State<T>
implements TickerProvider {
late Animation<double> animation, scaleAnimation;
late AnimationController _animationController, _scaleAnimationController;
late Animation<Color?> colorAnimation;
bool animationCompleted = false;
void _createAnimations() {
_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,
));
_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,
));
}
void forwardAnimations() {
_scaleAnimationController.forward().whenComplete(() {
_animationController.forward();
});
}
Widget buildAnimation() => 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.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
void initState() {
_createAnimations();
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
animationCompleted = true;
});
}
});
super.initState();
}
}
+4 -16
View File
@@ -11,15 +11,9 @@ dependencies:
flutter:
sdk: flutter
stream_chat_flutter:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: ref/segregate-api-layer
path: packages/stream_chat_flutter
path: ../../../stream-chat-flutter/packages/stream_chat_flutter
stream_chat_persistence:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: ref/segregate-api-layer
path: packages/stream_chat_persistence
path: ../../../stream-chat-flutter/packages/stream_chat_persistence
flutter_local_notifications: ^5.0.0+4
flutter_svg: ^0.22.0
flutter_secure_storage: ^4.2.0
@@ -31,15 +25,9 @@ dependencies:
dependency_overrides:
stream_chat:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: ref/segregate-api-layer
path: packages/stream_chat
path: ../../../stream-chat-flutter/packages/stream_chat
stream_chat_flutter_core:
git:
url: https://github.com/GetStream/stream-chat-flutter.git
ref: ref/segregate-api-layer
path: packages/stream_chat_flutter_core
path: ../../../stream-chat-flutter/packages/stream_chat_flutter_core
dev_dependencies:
flutter_launcher_icons: ^0.9.0