refactor: use gorouter for navigation

This commit is contained in:
Gordon Hayes
2022-10-17 15:01:03 +02:00
parent 852bb68f1b
commit bea73ec6a1
27 changed files with 922 additions and 915 deletions
@@ -1,13 +1,15 @@
import 'package:example/app.dart';
import 'package:example/pages/home_page.dart';
import 'package:example/state/init_data.dart';
import 'package:example/utils/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:example/widgets/stream_version.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'choose_user_page.dart';
import 'package:example/pages/choose_user_page.dart';
class AdvancedOptionsPage extends StatefulWidget {
const AdvancedOptionsPage({super.key});
@@ -41,6 +43,86 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
super.dispose();
}
Future<void> _login() async {
if (loading) {
return;
}
if (_formKey.currentState!.validate()) {
final apiKey = _apiKeyController.text;
final userId = _userIdController.text;
final userToken = _userTokenController.text;
final username = _usernameController.text;
loading = true;
showDialog(
barrierDismissible: false,
context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
builder: (context) => Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: StreamChatTheme.of(context).colorTheme.barsBg,
),
height: 100,
width: 100,
child: const Center(
child: CircularProgressIndicator(),
),
),
),
);
final client = buildStreamChatClient(apiKey);
final router = GoRouter.of(context);
final initNotifier = context.read<InitNotifier>();
try {
await client.connectUser(
User(
id: userId,
extraData: {
'name': username,
},
),
userToken,
);
const secureStorage = FlutterSecureStorage();
await Future.wait([
secureStorage.write(
key: kStreamApiKey,
value: apiKey,
),
secureStorage.write(
key: kStreamUserId,
value: userId,
),
secureStorage.write(
key: kStreamToken,
value: userToken,
),
]);
} catch (e) {
debugPrint(e.toString());
var errorText = AppLocalizations.of(context).errorConnecting;
if (e is Map) {
errorText = e['message'] ?? errorText;
}
Navigator.of(context).pop();
setState(() {
_apiKeyError = errorText.toUpperCase();
});
loading = false;
return;
}
loading = false;
initNotifier.initData = initNotifier.initData!.copyWith(client: client);
router.goNamed(Routes.CHOOSE_USER.name);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -260,6 +342,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
),
),
),
onPressed: _login,
child: Text(
AppLocalizations.of(context).login,
style: TextStyle(
@@ -271,84 +354,6 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
: Colors.white,
),
),
onPressed: () async {
if (loading) {
return;
}
if (_formKey.currentState!.validate()) {
final apiKey = _apiKeyController.text;
final userId = _userIdController.text;
final userToken = _userTokenController.text;
final username = _usernameController.text;
loading = true;
showDialog(
barrierDismissible: false,
context: context,
barrierColor:
StreamChatTheme.of(context).colorTheme.overlay,
builder: (context) => Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: StreamChatTheme.of(context)
.colorTheme
.barsBg,
),
height: 100,
width: 100,
child: const Center(
child: CircularProgressIndicator(),
),
),
),
);
final client = buildStreamChatClient(apiKey);
final navigator = Navigator.of(context);
try {
await client.connectUser(
User(id: userId, extraData: {
'name': username,
}),
userToken,
);
const secureStorage = FlutterSecureStorage();
secureStorage.write(
key: kStreamApiKey,
value: apiKey,
);
secureStorage.write(
key: kStreamUserId,
value: userId,
);
secureStorage.write(
key: kStreamToken,
value: userToken,
);
} catch (e) {
var errorText =
AppLocalizations.of(context).errorConnecting;
if (e is Map) {
errorText = e['message'] ?? errorText;
}
Navigator.pop(context);
setState(() {
_apiKeyError = errorText.toUpperCase();
});
loading = false;
return;
}
loading = false;
await navigator.pushNamedAndRemoveUntil(
Routes.HOME,
ModalRoute.withName(Routes.HOME),
arguments: HomePageArgs(client),
);
}
},
),
const StreamVersion(),
],
@@ -1,17 +1,21 @@
import 'dart:async';
import 'package:example/utils/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:example/app.dart';
import 'package:example/state/init_data.dart';
import 'package:example/pages/user_mentions_page.dart';
import 'package:example/routes/routes.dart';
import 'package:example/utils/app_config.dart';
import 'package:example/utils/localizations.dart';
import 'package:example/widgets/channel_list.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:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
import 'package:example/widgets/channel_list.dart';
class ChannelListPage extends StatefulWidget {
const ChannelListPage({
Key? key,
@@ -71,12 +75,10 @@ class _ChannelListPageState extends State<ChannelListPage> {
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: StreamChannelListHeader(
onNewChatButtonTap: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
preNavigationCallback: () {
FocusScope.of(context).requestFocus(FocusNode());
},
onNewChatButtonTap: () =>
GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name),
preNavigationCallback: () =>
FocusScope.of(context).requestFocus(FocusNode()),
),
drawer: LeftDrawer(
user: user,
@@ -188,10 +190,8 @@ class LeftDrawer extends StatelessWidget {
.withOpacity(.5),
),
onTap: () {
Navigator.popAndPushNamed(
context,
Routes.NEW_CHAT,
);
Navigator.of(context).pop();
GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name);
},
title: Text(
AppLocalizations.of(context).newDirectMessage,
@@ -208,10 +208,8 @@ class LeftDrawer extends StatelessWidget {
.withOpacity(.5),
),
onTap: () {
Navigator.popAndPushNamed(
context,
Routes.NEW_GROUP_CHAT,
);
Navigator.of(context).pop();
GoRouter.of(context).pushNamed(Routes.NEW_GROUP_CHAT.name);
},
title: Text(
AppLocalizations.of(context).newGroup,
@@ -226,22 +224,21 @@ class LeftDrawer extends StatelessWidget {
child: ListTile(
onTap: () async {
final client = StreamChat.of(context).client;
final navigator =
Navigator.of(context, rootNavigator: true);
Navigator.pop(context);
final router = GoRouter.of(context);
final initNotifier = context.read<InitNotifier>();
if (!kIsWeb) {
const secureStorage = FlutterSecureStorage();
await secureStorage.deleteAll();
}
client.disconnectUser();
await client.disconnectUser(flushChatPersistence: true);
await client.dispose();
initNotifier.initData = initNotifier.initData!.copyWith(
client:
buildStreamChatClient(kDefaultStreamApiKey));
await navigator.pushNamedAndRemoveUntil(
Routes.CHOOSE_USER,
ModalRoute.withName(Routes.CHOOSE_USER),
);
router.goNamed(Routes.CHOOSE_USER.name);
},
leading: StreamSvgIcon.user(
color: StreamChatTheme.of(context)
@@ -1,10 +1,10 @@
import 'package:example/utils/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
import 'channel_page.dart';
import '../routes/routes.dart';
class ChannelMediaDisplayScreen extends StatefulWidget {
final StreamMessageThemeData messageTheme;
@@ -156,23 +156,16 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
startIndex: position,
userName: media[position].message.user!.name,
onShowMessage: (m, c) async {
final client =
StreamChat.of(context).client;
final navigator = Navigator.of(context);
final message = m;
final channel = client.channel(
c.type,
id: c.id,
);
final router = GoRouter.of(context);
if (channel.state == null) {
await channel.watch();
}
navigator.pushNamed(
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
router.pushNamed(
Routes.CHANNEL_PAGE.name,
params:
Routes.CHANNEL_PAGE.params(channel),
queryParams:
Routes.CHANNEL_PAGE.queryParams(m),
);
},
),
@@ -2,21 +2,9 @@ import 'package:collection/collection.dart';
import 'package:example/routes/routes.dart';
import 'package:example/pages/thread_page.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'chat_info_screen.dart';
import 'group_info_screen.dart';
class ChannelPageArgs {
final Channel? channel;
final Message? initialMessage;
const ChannelPageArgs({
this.channel,
this.initialMessage,
});
}
class ChannelPage extends StatefulWidget {
final int? initialScrollIndex;
final double? initialAlignment;
@@ -63,9 +51,10 @@ class _ChannelPageState extends State<ChannelPage> {
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: StreamChannelHeader(
showTypingIndicator: false,
onBackPressed: () => GoRouter.of(context).pop(),
onImageTap: () async {
final channel = StreamChannel.of(context).channel;
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
if (channel.memberCount == 2 && channel.isDistinct) {
final currentUser = StreamChat.of(context).currentUser;
@@ -73,34 +62,16 @@ class _ChannelPageState extends State<ChannelPage> {
(element) => element.user!.id != currentUser!.id,
);
if (otherUser != null) {
final pop = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: ChatInfoScreen(
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
user: otherUser.user,
),
),
),
router.pushNamed(
Routes.CHAT_INFO_SCREEN.name,
params: Routes.CHAT_INFO_SCREEN.params(channel),
extra: otherUser.user,
);
if (pop == true) {
navigator.pop();
}
}
} else {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: GroupInfoScreen(
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
),
),
),
GoRouter.of(context).pushNamed(
Routes.GROUP_INFO_SCREEN.name,
params: Routes.GROUP_INFO_SCREEN.params(channel),
);
}
},
@@ -117,7 +88,7 @@ class _ChannelPageState extends State<ChannelPage> {
onMessageSwiped: _reply,
messageFilter: defaultFilter,
messageBuilder: (context, details, messages, defaultMessage) {
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
return defaultMessage.copyWith(
onReplyTap: _reply,
onShowMessage: (m, c) async {
@@ -130,12 +101,10 @@ class _ChannelPageState extends State<ChannelPage> {
if (channel.state == null) {
await channel.watch();
}
navigator.pushReplacementNamed(
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
router.goNamed(
Routes.CHANNEL_PAGE.name,
params: Routes.CHANNEL_PAGE.params(channel),
queryParams: Routes.CHANNEL_PAGE.queryParams(message),
);
},
deletedBottomRowBuilder: (context, message) {
@@ -1,12 +1,13 @@
import 'package:example/app.dart';
import 'package:example/state/init_data.dart';
import 'package:example/utils/app_config.dart';
import 'package:example/pages/home_page.dart';
import 'package:example/utils/localizations.dart';
import 'package:example/widgets/stream_version.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import '../routes/routes.dart';
@@ -94,12 +95,10 @@ class ChooseUserPage extends StatelessWidget {
),
);
final client = StreamChatClient(
kDefaultStreamApiKey,
logLevel: Level.INFO,
)..chatPersistenceClient = chatPersistentClient;
final client =
context.read<InitNotifier>().initData!.client;
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
await client.connectUser(
user,
@@ -121,11 +120,7 @@ class ChooseUserPage extends StatelessWidget {
value: token,
);
}
navigator.pushNamedAndRemoveUntil(
Routes.HOME,
ModalRoute.withName(Routes.HOME),
arguments: HomePageArgs(client),
);
router.replaceNamed(Routes.CHANNEL_LIST_PAGE.name);
},
leading: StreamUserAvatar(
user: user,
@@ -157,9 +152,8 @@ class ChooseUserPage extends StatelessWidget {
);
}),
ListTile(
onTap: () {
Navigator.pushNamed(context, Routes.ADVANCED_OPTIONS);
},
onTap: () => GoRouter.of(context)
.pushNamed(Routes.ADVANCED_OPTIONS.name),
leading: CircleAvatar(
backgroundColor:
StreamChatTheme.of(context).colorTheme.borders,
@@ -1,16 +1,17 @@
import 'package:example/state/new_group_chat_state.dart';
import 'package:example/utils/localizations.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'channel_page.dart';
import '../routes/routes.dart';
class GroupChatDetailsScreen extends StatefulWidget {
final List<User>? selectedUsers;
final NewGroupChatState groupChatState;
const GroupChatDetailsScreen({
Key? key,
required this.selectedUsers,
required this.groupChatState,
}) : super(key: key);
@override
@@ -18,14 +19,12 @@ class GroupChatDetailsScreen extends StatefulWidget {
}
class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
late final _selectedUsers = <User>[...?widget.selectedUsers];
late final TextEditingController _groupNameController =
TextEditingController()..addListener(_groupNameListener);
bool _isGroupNameEmpty = true;
int get _totalUsers => _selectedUsers.length;
int get _totalUsers => widget.groupChatState.users.length;
void _groupNameListener() {
final name = _groupNameController.text;
@@ -48,7 +47,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context, _selectedUsers);
GoRouter.of(context).pop();
return false;
},
child: Scaffold(
@@ -123,21 +122,21 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
try {
final groupName = _groupNameController.text;
final client = StreamChat.of(context).client;
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
final channel = client.channel('messaging',
id: const Uuid().v4(),
extraData: {
'members': [
client.state.currentUser!.id,
..._selectedUsers.map((e) => e.id),
...widget.groupChatState.users
.map((e) => e.id),
],
'name': groupName,
});
await channel.watch();
navigator.pushNamedAndRemoveUntil(
Routes.CHANNEL_PAGE,
ModalRoute.withName(Routes.CHANNEL_LIST_PAGE),
arguments: ChannelPageArgs(channel: channel),
router.goNamed(
Routes.CHANNEL_PAGE.name,
params: Routes.CHANNEL_PAGE.params(channel),
);
} catch (err) {
_showErrorAlert();
@@ -192,67 +191,73 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
),
),
),
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: ListView.separated(
itemCount: _selectedUsers.length + 1,
separatorBuilder: (_, __) => Container(
height: 1,
color: StreamChatTheme.of(context).colorTheme.borders,
),
itemBuilder: (_, index) {
if (index == _selectedUsers.length) {
return Container(
height: 1,
color: StreamChatTheme.of(context)
.colorTheme
.borders,
);
}
final user = _selectedUsers[index];
return ListTile(
key: ObjectKey(user),
leading: StreamUserAvatar(
user: user,
constraints: const BoxConstraints.tightFor(
width: 40,
height: 40,
),
),
title: Text(
user.name,
style:
const TextStyle(fontWeight: FontWeight.bold),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
trailing: IconButton(
icon: Icon(
Icons.clear_rounded,
AnimatedBuilder(
animation: widget.groupChatState,
builder: (context, child) {
return Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: ListView.separated(
itemCount: widget.groupChatState.users.length + 1,
separatorBuilder: (_, __) => Container(
height: 1,
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis,
.borders,
),
padding: const EdgeInsets.all(0),
splashRadius: 24,
onPressed: () {
setState(() {
_selectedUsers.remove(user);
});
if (_selectedUsers.isEmpty) {
Navigator.pop(context, _selectedUsers);
itemBuilder: (_, index) {
if (index ==
widget.groupChatState.users.length) {
return Container(
height: 1,
color: StreamChatTheme.of(context)
.colorTheme
.borders,
);
}
final user = widget.groupChatState.users
.elementAt(index);
return ListTile(
key: ObjectKey(user),
leading: StreamUserAvatar(
user: user,
constraints: const BoxConstraints.tightFor(
width: 40,
height: 40,
),
),
title: Text(
user.name,
style: const TextStyle(
fontWeight: FontWeight.bold),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
trailing: IconButton(
icon: Icon(
Icons.clear_rounded,
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis,
),
padding: const EdgeInsets.all(0),
splashRadius: 24,
onPressed: () {
widget.groupChatState.removeUser(user);
if (widget.groupChatState.users.isEmpty) {
GoRouter.of(context).pop();
}
},
),
);
},
),
);
},
),
),
),
),
);
}),
],
),
);
@@ -308,6 +313,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: GoRouter.of(context).pop,
child: Text(
AppLocalizations.of(context).ok,
style: StreamChatTheme.of(context)
@@ -318,9 +324,6 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
.colorTheme
.accentPrimary),
),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
@@ -2,14 +2,14 @@ import 'dart:async';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:example/pages/channel_file_display_screen.dart';
import 'package:example/routes/routes.dart';
import 'package:example/utils/localizations.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'channel_media_display_screen.dart';
import 'channel_page.dart';
import 'chat_info_screen.dart';
import 'pinned_messages_screen.dart';
class GroupInfoScreen extends StatefulWidget {
@@ -647,7 +647,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
onTap: () async {
final streamChannel = StreamChannel.of(context);
final streamChat = StreamChat.of(context);
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
final res = await showConfirmationBottomSheet(
context,
title: AppLocalizations.of(context).leaveConversation,
@@ -662,7 +662,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
if (res == true) {
final channel = streamChannel.channel;
await channel.removeMembers([streamChat.currentUser!.id]);
navigator.pop();
router.pop();
}
},
),
@@ -865,7 +865,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
AppLocalizations.of(context).viewInfo,
() async {
final client = StreamChat.of(context).client;
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
final c = client.channel('messaging', extraData: {
'members': [
@@ -876,16 +876,10 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
await c.watch();
await navigator.push(
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: c,
child: ChatInfoScreen(
messageTheme: widget.messageTheme,
user: user,
),
),
),
router.pushNamed(
Routes.CHAT_INFO_SCREEN.name,
params: Routes.CHAT_INFO_SCREEN.params(c),
extra: user,
);
},
),
@@ -901,7 +895,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
AppLocalizations.of(context).message,
() async {
final client = StreamChat.of(context).client;
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
final c = client.channel('messaging', extraData: {
'members': [
@@ -912,13 +906,9 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
await c.watch();
await navigator.push(
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: c,
child: const ChannelPage(),
),
),
router.pushNamed(
Routes.CHANNEL_PAGE.name,
params: Routes.CHANNEL_PAGE.params(c),
);
},
),
@@ -934,7 +924,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
size: 24.0,
),
AppLocalizations.of(context).removeFromGroup, () async {
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
final res = await showConfirmationBottomSheet(
context,
title: AppLocalizations.of(context).removeMember,
@@ -949,21 +939,23 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
if (res == true) {
await channel.removeMembers([user.id]);
}
navigator.pop();
router.pop();
},
color:
StreamChatTheme.of(context).colorTheme.accentError),
_buildModalListTile(
context,
StreamSvgIcon.closeSmall(
color: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
size: 24.0,
),
AppLocalizations.of(context).cancel, () {
Navigator.pop(context);
}),
context,
StreamSvgIcon.closeSmall(
color: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
size: 24.0,
),
AppLocalizations.of(context).cancel,
() {
Navigator.pop(context);
},
),
],
),
),
@@ -1,115 +0,0 @@
import 'dart:async';
import 'package:example/pages/channel_page.dart';
import 'package:example/utils/notifications_service.dart';
import 'package:example/routes/app_routes.dart';
import 'package:example/routes/routes.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class MyObserver extends NavigatorObserver {
Route? currentRoute;
late final StreamSubscription _subscription;
MyObserver(
StreamChatClient client,
GlobalKey<NavigatorState> navigatorKey,
) {
_subscription = client
.on(
EventType.messageNew,
EventType.notificationMessageNew,
)
.listen((event) {
if (event.message?.user?.id == client.state.currentUser?.id) {
return;
}
final channelId = event.channelId;
if (currentRoute?.settings.name == Routes.CHANNEL_PAGE) {
final args = currentRoute?.settings.arguments as ChannelPageArgs;
if (args.channel?.id == channelId) {
return;
}
}
showLocalNotification(
event,
client.state.currentUser!.id,
navigatorKey.currentState!.context,
);
});
}
@override
void didPop(Route route, Route? previousRoute) {
currentRoute = route;
}
@override
void didPush(Route route, Route? previousRoute) {
currentRoute = route;
}
@override
void didRemove(Route route, Route? previousRoute) {
currentRoute = route;
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
currentRoute = newRoute;
}
void dispose() {
_subscription.cancel();
}
}
class HomePageArgs {
final StreamChatClient chatClient;
HomePageArgs(this.chatClient);
}
class HomePage extends StatefulWidget {
const HomePage({
Key? key,
required this.chatClient,
}) : super(key: key);
final StreamChatClient chatClient;
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
MyObserver? _observer;
@override
Widget build(BuildContext context) {
return StreamChat(
client: widget.chatClient,
child: WillPopScope(
onWillPop: () async {
final canPop = await _navigatorKey.currentState?.maybePop() ?? false;
return !canPop;
},
child: Navigator(
key: _navigatorKey,
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: Routes.CHANNEL_LIST_PAGE,
observers: [_observer!],
),
),
);
}
@override
void didChangeDependencies() {
_observer?.dispose();
_observer = MyObserver(widget.chatClient, _navigatorKey);
super.didChangeDependencies();
}
}
@@ -2,9 +2,9 @@ import 'dart:async';
import 'package:example/utils/localizations.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'channel_page.dart';
import '../widgets/chips_input_text_field.dart';
import '../routes/routes.dart';
@@ -61,13 +61,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
_userNameQuery = _controller.text;
_isSearchActive = _userNameQuery.isNotEmpty;
});
userListController.filter = Filter.and([
if (_userNameQuery.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]);
userListController.doInitialLoad();
}
userListController.filter = Filter.and([
if (_userNameQuery.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]);
userListController.doInitialLoad();
});
}
@@ -248,9 +249,8 @@ class _NewChatScreenState extends State<NewChatScreen> {
if (!_isSearchActive && !_selectedUsers.isNotEmpty)
InkWell(
onTap: () {
Navigator.pushNamed(
context,
Routes.NEW_GROUP_CHAT,
GoRouter.of(context).pushNamed(
Routes.NEW_GROUP_CHAT.name,
);
},
child: Padding(
@@ -311,8 +311,6 @@ class _NewChatScreenState extends State<NewChatScreen> {
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: StreamUserListView(
controller: userListController,
// groupAlphabetically:
// _isSearchActive ? false : true,
onUserTap: (user) {
_controller.clear();
if (!_selectedUsers.contains(user)) {
@@ -409,11 +407,9 @@ class _NewChatScreenState extends State<NewChatScreen> {
return message;
},
onMessageSent: (m) {
Navigator.pushNamedAndRemoveUntil(
context,
Routes.CHANNEL_PAGE,
ModalRoute.withName(Routes.CHANNEL_LIST_PAGE),
arguments: ChannelPageArgs(channel: channel),
GoRouter.of(context).goNamed(
Routes.CHANNEL_PAGE.name,
params: Routes.CHANNEL_PAGE.params(channel!),
);
},
),
@@ -1,7 +1,9 @@
import 'dart:async';
import 'package:example/state/new_group_chat_state.dart';
import 'package:example/utils/localizations.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import '../routes/routes.dart';
@@ -20,7 +22,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
String _userNameQuery = '';
final _selectedUsers = <User>{};
final groupChatState = NewGroupChatState();
bool _isSearchActive = false;
@@ -69,250 +71,240 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: AppBar(
elevation: 1,
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
leading: const StreamBackButton(),
title: Text(
AppLocalizations.of(context).addGroupMembers,
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
fontSize: 16,
),
),
centerTitle: true,
actions: [
if (_selectedUsers.isNotEmpty)
IconButton(
icon: StreamSvgIcon.arrowRight(
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
return AnimatedBuilder(
animation: groupChatState,
builder: (context, child) {
final state = groupChatState;
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: AppBar(
elevation: 1,
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
leading: const StreamBackButton(),
title: Text(
AppLocalizations.of(context).addGroupMembers,
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
fontSize: 16,
),
onPressed: () async {
final updatedList = await Navigator.pushNamed(
context,
Routes.NEW_GROUP_CHAT_DETAILS,
arguments: _selectedUsers.toList(growable: false),
);
if (updatedList != null) {
setState(() {
_selectedUsers
..clear()
..addAll(updatedList as Iterable<User>);
});
}
},
)
],
),
body: StreamConnectionStatusBuilder(
statusBuilder: (context, status) {
String statusString = '';
bool showStatus = true;
switch (status) {
case ConnectionStatus.connected:
statusString = AppLocalizations.of(context).connected;
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = AppLocalizations.of(context).reconnecting;
break;
case ConnectionStatus.disconnected:
statusString = AppLocalizations.of(context).disconnected;
break;
}
return StreamInfoTile(
showMessage: showStatus,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: statusString,
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverToBoxAdapter(
child: SearchTextField(
controller: _controller,
hintText: AppLocalizations.of(context).search,
),
),
centerTitle: true,
actions: [
if (state.users.isNotEmpty)
IconButton(
icon: StreamSvgIcon.arrowRight(
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
),
if (_selectedUsers.isNotEmpty)
SliverToBoxAdapter(
child: SizedBox(
height: 104,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _selectedUsers.length,
padding: const EdgeInsets.all(8),
separatorBuilder: (_, __) =>
const SizedBox(width: 16),
itemBuilder: (_, index) {
final user = _selectedUsers.elementAt(index);
return Column(
children: [
Stack(
children: [
StreamUserAvatar(
onlineIndicatorAlignment:
const Alignment(0.9, 0.9),
user: user,
showOnlineStatus: true,
borderRadius: BorderRadius.circular(32),
constraints:
const BoxConstraints.tightFor(
height: 64,
width: 64,
),
),
Positioned(
top: -4,
right: -4,
child: GestureDetector(
onTap: () {
if (_selectedUsers.contains(user)) {
setState(() =>
_selectedUsers.remove(user));
}
},
child: Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context)
.colorTheme
.appBg,
shape: BoxShape.circle,
border: Border.all(
color: StreamChatTheme.of(context)
.colorTheme
.appBg,
),
),
child: StreamSvgIcon.close(
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis,
size: 24,
),
),
),
)
],
),
const SizedBox(height: 4),
Text(
user.name.split(' ')[0],
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
);
},
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _HeaderDelegate(
height: 32,
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient:
StreamChatTheme.of(context).colorTheme.bgGradient,
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
_isSearchActive
? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"'
: AppLocalizations.of(context).onThePlatorm,
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
),
),
),
),
),
),
];
},
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: StreamUserListView(
controller: userListController,
itemBuilder: (context, items, index, defaultWidget) {
return defaultWidget.copyWith(
selected: _selectedUsers.contains(items[index]),
onPressed: () async {
GoRouter.of(context).pushNamed(
Routes.NEW_GROUP_CHAT_DETAILS.name,
extra: state,
);
},
onUserTap: (user) {
if (!_selectedUsers.contains(user)) {
setState(() {
_selectedUsers.add(user);
});
} else {
setState(() {
_selectedUsers.remove(user);
});
}
},
emptyBuilder: (_) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: const 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: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
)
],
),
body: StreamConnectionStatusBuilder(
statusBuilder: (context, status) {
String statusString = '';
bool showStatus = true;
switch (status) {
case ConnectionStatus.connected:
statusString = AppLocalizations.of(context).connected;
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = AppLocalizations.of(context).reconnecting;
break;
case ConnectionStatus.disconnected:
statusString = AppLocalizations.of(context).disconnected;
break;
}
return StreamInfoTile(
showMessage: showStatus,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: statusString,
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverToBoxAdapter(
child: SearchTextField(
controller: _controller,
hintText: AppLocalizations.of(context).search,
),
),
if (state.users.isNotEmpty)
SliverToBoxAdapter(
child: SizedBox(
height: 104,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: state.users.length,
padding: const EdgeInsets.all(8),
separatorBuilder: (_, __) =>
const SizedBox(width: 16),
itemBuilder: (_, index) {
final user = state.users.elementAt(index);
return Column(
children: [
Stack(
children: [
StreamUserAvatar(
onlineIndicatorAlignment:
const Alignment(0.9, 0.9),
user: user,
showOnlineStatus: true,
borderRadius:
BorderRadius.circular(32),
constraints:
const BoxConstraints.tightFor(
height: 64,
width: 64,
),
),
Positioned(
top: -4,
right: -4,
child: GestureDetector(
onTap: () {
groupChatState.removeUser(user);
},
child: Container(
decoration: BoxDecoration(
color:
StreamChatTheme.of(context)
.colorTheme
.appBg,
shape: BoxShape.circle,
border: Border.all(
color: StreamChatTheme.of(
context)
.colorTheme
.appBg,
),
),
child: StreamSvgIcon.close(
color:
StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis,
size: 24,
),
),
),
)
],
),
),
Text(
AppLocalizations.of(context)
.noUserMatchesTheseKeywords,
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
const SizedBox(height: 4),
Text(
user.name.split(' ')[0],
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
);
},
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _HeaderDelegate(
height: 32,
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient: StreamChatTheme.of(context)
.colorTheme
.bgGradient,
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
_isSearchActive
? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"'
: AppLocalizations.of(context).onThePlatorm,
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
),
),
),
),
),
),
];
},
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: StreamUserListView(
controller: userListController,
itemBuilder: (context, items, index, defaultWidget) {
return defaultWidget.copyWith(
selected: state.users.contains(items[index]),
);
},
onUserTap: groupChatState.addOrRemoveUser,
emptyBuilder: (_) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: const 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: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
),
),
Text(
AppLocalizations.of(context)
.noUserMatchesTheseKeywords,
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
),
),
],
),
],
),
),
),
),
);
},
);
},
);
},
),
),
),
),
),
);
},
),
);
},
),
);
},
);
}
}
@@ -1,10 +1,9 @@
import 'package:example/utils/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'channel_page.dart';
class PinnedMessagesScreen extends StatefulWidget {
const PinnedMessagesScreen({super.key});
@@ -103,7 +102,7 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
},
onMessageTap: (messageResponse) async {
final client = StreamChat.of(context).client;
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
final message = messageResponse.message;
final channel = client.channel(
messageResponse.channel!.type,
@@ -112,12 +111,10 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
if (channel.state == null) {
await channel.watch();
}
navigator.pushNamed(
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
router.pushNamed(
Routes.CHANNEL_PAGE.name,
params: Routes.CHANNEL_PAGE.params(channel),
queryParams: Routes.CHANNEL_PAGE.queryParams(message),
);
},
),
@@ -1,10 +1,9 @@
import 'package:example/utils/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'channel_page.dart';
class UserMentionsPage extends StatefulWidget {
const UserMentionsPage({super.key});
@@ -72,7 +71,7 @@ class _UserMentionsPageState extends State<UserMentionsPage> {
},
onMessageTap: (messageResponse) async {
final client = StreamChat.of(context).client;
final navigator = Navigator.of(context);
final router = GoRouter.of(context);
final message = messageResponse.message;
final channel = client.channel(
messageResponse.channel!.type,
@@ -81,12 +80,10 @@ class _UserMentionsPageState extends State<UserMentionsPage> {
if (channel.state == null) {
await channel.watch();
}
navigator.pushNamed(
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
router.pushNamed(
Routes.CHANNEL_PAGE.name,
params: Routes.CHANNEL_PAGE.params(channel),
queryParams: Routes.CHANNEL_PAGE.queryParams(message),
);
},
);