mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-10-30 19:47:23 +01:00 
			
		
		
		
	feat: Implement discover groups page
This commit is contained in:
		
							parent
							
								
									adb445f668
								
							
						
					
					
						commit
						e728ccc1ba
					
				
							
								
								
									
										54
									
								
								lib/components/default_app_bar_search_field.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								lib/components/default_app_bar_search_field.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/l10n.dart'; | ||||
| 
 | ||||
| class DefaultAppBarSearchField extends StatelessWidget { | ||||
|   final TextEditingController searchController; | ||||
|   final void Function(String) onChanged; | ||||
|   final Widget suffix; | ||||
| 
 | ||||
|   const DefaultAppBarSearchField({ | ||||
|     Key key, | ||||
|     this.searchController, | ||||
|     this.onChanged, | ||||
|     this.suffix, | ||||
|   }) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final focusNode = FocusNode(); | ||||
|     return Container( | ||||
|       height: 40, | ||||
|       padding: EdgeInsets.only(right: 16), | ||||
|       child: Material( | ||||
|         color: Theme.of(context).secondaryHeaderColor, | ||||
|         borderRadius: BorderRadius.circular(32), | ||||
|         child: TextField( | ||||
|           autocorrect: false, | ||||
|           controller: searchController, | ||||
|           onChanged: onChanged, | ||||
|           focusNode: focusNode, | ||||
|           decoration: InputDecoration( | ||||
|             contentPadding: EdgeInsets.only( | ||||
|               top: 8, | ||||
|               bottom: 8, | ||||
|               left: 16, | ||||
|             ), | ||||
|             border: OutlineInputBorder( | ||||
|               borderRadius: BorderRadius.circular(32), | ||||
|             ), | ||||
|             hintText: L10n.of(context).searchForAChat, | ||||
|             suffixIcon: focusNode.hasFocus | ||||
|                 ? IconButton( | ||||
|                     icon: Icon(Icons.backspace_outlined), | ||||
|                     onPressed: () { | ||||
|                       searchController.clear(); | ||||
|                       focusNode.unfocus(); | ||||
|                     }, | ||||
|                   ) | ||||
|                 : suffix, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										104
									
								
								lib/components/default_drawer.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								lib/components/default_drawer.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | ||||
| import 'package:adaptive_dialog/adaptive_dialog.dart'; | ||||
| import 'package:famedlysdk/famedlysdk.dart'; | ||||
| import 'package:fluffychat/utils/app_route.dart'; | ||||
| import 'package:fluffychat/views/archive.dart'; | ||||
| import 'package:fluffychat/views/discover_view.dart'; | ||||
| import 'package:fluffychat/views/new_group.dart'; | ||||
| import 'package:fluffychat/views/new_private_chat.dart'; | ||||
| import 'package:fluffychat/views/settings.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/l10n.dart'; | ||||
| 
 | ||||
| import 'dialogs/simple_dialogs.dart'; | ||||
| import 'matrix.dart'; | ||||
| 
 | ||||
| class DefaultDrawer extends StatelessWidget { | ||||
|   void _drawerTapAction(BuildContext context, Widget view) { | ||||
|     Navigator.of(context).pop(); | ||||
|     Navigator.of(context).pushAndRemoveUntil( | ||||
|       AppRoute.defaultRoute( | ||||
|         context, | ||||
|         view, | ||||
|       ), | ||||
|       (r) => r.isFirst, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void _setStatus(BuildContext context) async { | ||||
|     Navigator.of(context).pop(); | ||||
|     final input = await showTextInputDialog( | ||||
|       title: L10n.of(context).setStatus, | ||||
|       context: context, | ||||
|       textFields: [ | ||||
|         DialogTextField( | ||||
|           hintText: L10n.of(context).statusExampleMessage, | ||||
|         ) | ||||
|       ], | ||||
|     ); | ||||
|     if (input == null || input.single.isEmpty) return; | ||||
|     final client = Matrix.of(context).client; | ||||
|     await SimpleDialogs(context).tryRequestWithLoadingDialog( | ||||
|       client.sendPresence( | ||||
|         client.userID, | ||||
|         PresenceType.online, | ||||
|         statusMsg: input.single, | ||||
|       ), | ||||
|     ); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Drawer( | ||||
|       child: SafeArea( | ||||
|         child: ListView( | ||||
|           padding: EdgeInsets.zero, | ||||
|           children: <Widget>[ | ||||
|             ListTile( | ||||
|               leading: Icon(Icons.edit_outlined), | ||||
|               title: Text(L10n.of(context).setStatus), | ||||
|               onTap: () => _setStatus(context), | ||||
|             ), | ||||
|             Divider(height: 1), | ||||
|             ListTile( | ||||
|               leading: Icon(Icons.people_outline), | ||||
|               title: Text(L10n.of(context).createNewGroup), | ||||
|               onTap: () => _drawerTapAction(context, NewGroupView()), | ||||
|             ), | ||||
|             ListTile( | ||||
|               leading: Icon(Icons.person_add_outlined), | ||||
|               title: Text(L10n.of(context).newPrivateChat), | ||||
|               onTap: () => _drawerTapAction(context, NewPrivateChatView()), | ||||
|             ), | ||||
|             Divider(height: 1), | ||||
|             ListTile( | ||||
|               leading: Icon(Icons.archive_outlined), | ||||
|               title: Text(L10n.of(context).archive), | ||||
|               onTap: () => _drawerTapAction( | ||||
|                 context, | ||||
|                 Archive(), | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               leading: Icon(Icons.group_work_outlined), | ||||
|               title: Text(L10n.of(context).discoverGroups), | ||||
|               onTap: () => _drawerTapAction( | ||||
|                 context, | ||||
|                 DiscoverView(), | ||||
|               ), | ||||
|             ), | ||||
|             Divider(height: 1), | ||||
|             ListTile( | ||||
|               leading: Icon(Icons.settings_outlined), | ||||
|               title: Text(L10n.of(context).settings), | ||||
|               onTap: () => _drawerTapAction( | ||||
|                 context, | ||||
|                 SettingsView(), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -1370,6 +1370,16 @@ | ||||
|       "count": {} | ||||
|     } | ||||
|   }, | ||||
|   "discoverGroups": "Discover groups", | ||||
|   "@discoverGroups": { | ||||
|     "type": "text", | ||||
|     "placeholders": {} | ||||
|   }, | ||||
|   "noDescription": "No description", | ||||
|   "@noDescription": { | ||||
|     "type": "text", | ||||
|     "placeholders": {} | ||||
|   }, | ||||
|   "editBlockedServers": "Edit blocked servers", | ||||
|   "@editBlockedServers": { | ||||
|     "type": "text", | ||||
|  | ||||
| @ -13,6 +13,9 @@ extension RoomStatusExtension on Room { | ||||
|           directChatPresence.presence != null && | ||||
|           (directChatPresence.presence.lastActiveAgo != null || | ||||
|               directChatPresence.presence.currentlyActive != null)) { | ||||
|         if (directChatPresence.presence.statusMsg?.isNotEmpty ?? false) { | ||||
|           return directChatPresence.presence.statusMsg; | ||||
|         } | ||||
|         if (directChatPresence.presence.currentlyActive == true) { | ||||
|           return L10n.of(context).currentlyActive; | ||||
|         } | ||||
|  | ||||
| @ -18,12 +18,28 @@ class _ArchiveState extends State<Archive> { | ||||
|     return await Matrix.of(context).client.archive; | ||||
|   } | ||||
| 
 | ||||
|   final ScrollController _scrollController = ScrollController(); | ||||
|   bool _scrolledToTop = true; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     _scrollController.addListener(() async { | ||||
|       if (_scrollController.position.pixels > 0 && _scrolledToTop) { | ||||
|         setState(() => _scrolledToTop = false); | ||||
|       } else if (_scrollController.position.pixels == 0 && !_scrolledToTop) { | ||||
|         setState(() => _scrolledToTop = true); | ||||
|       } | ||||
|     }); | ||||
|     super.initState(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AdaptivePageLayout( | ||||
|       firstScaffold: Scaffold( | ||||
|         appBar: AppBar( | ||||
|           title: Text(L10n.of(context).archive), | ||||
|           elevation: _scrolledToTop ? 0 : null, | ||||
|         ), | ||||
|         body: FutureBuilder<List<Room>>( | ||||
|           future: getArchive(context), | ||||
| @ -33,6 +49,7 @@ class _ArchiveState extends State<Archive> { | ||||
|             } else { | ||||
|               archive = snapshot.data; | ||||
|               return ListView.builder( | ||||
|                 controller: _scrollController, | ||||
|                 itemCount: archive.length, | ||||
|                 itemBuilder: (BuildContext context, int i) => ChatListItem( | ||||
|                     archive[i], | ||||
|  | ||||
| @ -3,12 +3,11 @@ import 'dart:io'; | ||||
| 
 | ||||
| import 'package:adaptive_dialog/adaptive_dialog.dart'; | ||||
| import 'package:famedlysdk/famedlysdk.dart'; | ||||
| import 'package:famedlysdk/matrix_api.dart'; | ||||
| import 'package:fluffychat/components/connection_status_header.dart'; | ||||
| import 'package:fluffychat/components/default_app_bar_search_field.dart'; | ||||
| import 'package:fluffychat/components/default_drawer.dart'; | ||||
| import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; | ||||
| import 'package:fluffychat/components/list_items/public_room_list_item.dart'; | ||||
| import 'package:fluffychat/config/app_config.dart'; | ||||
| import 'package:fluffychat/utils/fluffy_share.dart'; | ||||
| import 'package:fluffychat/utils/platform_infos.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -21,11 +20,9 @@ import '../components/matrix.dart'; | ||||
| import '../utils/app_route.dart'; | ||||
| import '../utils/matrix_file_extension.dart'; | ||||
| import '../utils/url_launcher.dart'; | ||||
| import 'archive.dart'; | ||||
| import 'empty_page.dart'; | ||||
| import 'homeserver_picker.dart'; | ||||
| import 'new_group.dart'; | ||||
| import 'new_private_chat.dart'; | ||||
| import 'settings.dart'; | ||||
| 
 | ||||
| enum SelectMode { normal, share, select } | ||||
| 
 | ||||
| @ -35,11 +32,7 @@ class ChatListView extends StatelessWidget { | ||||
|     return AdaptivePageLayout( | ||||
|       primaryPage: FocusPage.FIRST, | ||||
|       firstScaffold: ChatList(), | ||||
|       secondScaffold: Scaffold( | ||||
|         body: Center( | ||||
|           child: Image.asset('assets/logo.png', width: 100, height: 100), | ||||
|         ), | ||||
|       ), | ||||
|       secondScaffold: EmptyPage(), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -56,14 +49,10 @@ class ChatList extends StatefulWidget { | ||||
| class _ChatListState extends State<ChatList> { | ||||
|   bool get searchMode => searchController.text?.isNotEmpty ?? false; | ||||
|   final TextEditingController searchController = TextEditingController(); | ||||
|   final FocusNode _searchFocusNode = FocusNode(); | ||||
|   Timer coolDown; | ||||
|   PublicRoomsResponse publicRoomsResponse; | ||||
|   bool loadingPublicRooms = false; | ||||
|   String searchServer; | ||||
|   final _selectedRoomIds = <String>{}; | ||||
| 
 | ||||
|   final ScrollController _scrollController = ScrollController(); | ||||
|   bool _scrolledToTop = true; | ||||
| 
 | ||||
|   void _toggleSelection(String roomId) => | ||||
|       setState(() => _selectedRoomIds.contains(roomId) | ||||
| @ -78,8 +67,6 @@ class _ChatListState extends State<ChatList> { | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   bool _scrolledToTop = true; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     _scrollController.addListener(() async { | ||||
| @ -89,46 +76,6 @@ class _ChatListState extends State<ChatList> { | ||||
|         setState(() => _scrolledToTop = true); | ||||
|       } | ||||
|     }); | ||||
|     searchController.addListener(() { | ||||
|       coolDown?.cancel(); | ||||
|       if (searchController.text.isEmpty) { | ||||
|         setState(() { | ||||
|           loadingPublicRooms = false; | ||||
|           publicRoomsResponse = null; | ||||
|         }); | ||||
|         return; | ||||
|       } | ||||
|       coolDown = Timer(Duration(seconds: 1), () async { | ||||
|         setState(() => loadingPublicRooms = true); | ||||
|         final newPublicRoomsResponse = | ||||
|             await SimpleDialogs(context).tryRequestWithErrorToast( | ||||
|           Matrix.of(context).client.searchPublicRooms( | ||||
|                 limit: 30, | ||||
|                 includeAllNetworks: true, | ||||
|                 genericSearchTerm: searchController.text, | ||||
|                 server: searchServer, | ||||
|               ), | ||||
|         ); | ||||
|         setState(() { | ||||
|           loadingPublicRooms = false; | ||||
|           if (newPublicRoomsResponse != false) { | ||||
|             publicRoomsResponse = newPublicRoomsResponse; | ||||
|             if (searchController.text.isNotEmpty && | ||||
|                 searchController.text.isValidMatrixId && | ||||
|                 searchController.text.sigil == '#') { | ||||
|               publicRoomsResponse.chunk.add( | ||||
|                 PublicRoom.fromJson({ | ||||
|                   'aliases': [searchController.text], | ||||
|                   'name': searchController.text, | ||||
|                   'room_id': searchController.text, | ||||
|                 }), | ||||
|               ); | ||||
|             } | ||||
|           } | ||||
|         }); | ||||
|       }); | ||||
|       setState(() => null); | ||||
|     }); | ||||
|     _initReceiveSharingIntent(); | ||||
|     super.initState(); | ||||
|   } | ||||
| @ -186,45 +133,8 @@ class _ChatListState extends State<ChatList> { | ||||
|     ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText); | ||||
|   } | ||||
| 
 | ||||
|   void _drawerTapAction(Widget view) { | ||||
|     Navigator.of(context).pop(); | ||||
|     Navigator.of(context).pushAndRemoveUntil( | ||||
|       AppRoute.defaultRoute( | ||||
|         context, | ||||
|         view, | ||||
|       ), | ||||
|       (r) => r.isFirst, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void _setStatus(BuildContext context) async { | ||||
|     Navigator.of(context).pop(); | ||||
|     final input = await showTextInputDialog( | ||||
|       title: L10n.of(context).setStatus, | ||||
|       context: context, | ||||
|       textFields: [ | ||||
|         DialogTextField( | ||||
|           hintText: L10n.of(context).statusExampleMessage, | ||||
|         ) | ||||
|       ], | ||||
|     ); | ||||
|     if (input == null || input.single.isEmpty) return; | ||||
|     final client = Matrix.of(context).client; | ||||
|     await SimpleDialogs(context).tryRequestWithLoadingDialog( | ||||
|       client.sendPresence( | ||||
|         client.userID, | ||||
|         PresenceType.online, | ||||
|         statusMsg: input.single, | ||||
|       ), | ||||
|     ); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void dispose() { | ||||
|     searchController.removeListener( | ||||
|       () => setState(() => null), | ||||
|     ); | ||||
|     _intentDataStreamSubscription?.cancel(); | ||||
|     _intentFileStreamSubscription?.cancel(); | ||||
|     super.dispose(); | ||||
| @ -292,62 +202,8 @@ class _ChatListState extends State<ChatList> { | ||||
|                   _selectedRoomIds.clear(); | ||||
|                 } | ||||
|                 return Scaffold( | ||||
|                   drawer: selectMode != SelectMode.normal | ||||
|                       ? null | ||||
|                       : Drawer( | ||||
|                           child: SafeArea( | ||||
|                             child: ListView( | ||||
|                               padding: EdgeInsets.zero, | ||||
|                               children: <Widget>[ | ||||
|                                 ListTile( | ||||
|                                   leading: Icon(Icons.edit_outlined), | ||||
|                                   title: Text(L10n.of(context).setStatus), | ||||
|                                   onTap: () => _setStatus(context), | ||||
|                                 ), | ||||
|                                 Divider(height: 1), | ||||
|                                 ListTile( | ||||
|                                   leading: Icon(Icons.people_outline), | ||||
|                                   title: Text(L10n.of(context).createNewGroup), | ||||
|                                   onTap: () => _drawerTapAction(NewGroupView()), | ||||
|                                 ), | ||||
|                                 ListTile( | ||||
|                                   leading: Icon(Icons.person_add_outlined), | ||||
|                                   title: Text(L10n.of(context).newPrivateChat), | ||||
|                                   onTap: () => | ||||
|                                       _drawerTapAction(NewPrivateChatView()), | ||||
|                                 ), | ||||
|                                 Divider(height: 1), | ||||
|                                 ListTile( | ||||
|                                   leading: Icon(Icons.archive_outlined), | ||||
|                                   title: Text(L10n.of(context).archive), | ||||
|                                   onTap: () => _drawerTapAction( | ||||
|                                     Archive(), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                                 ListTile( | ||||
|                                   leading: Icon(Icons.settings_outlined), | ||||
|                                   title: Text(L10n.of(context).settings), | ||||
|                                   onTap: () => _drawerTapAction( | ||||
|                                     SettingsView(), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                                 Divider(height: 1), | ||||
|                                 ListTile( | ||||
|                                   leading: Icon(Icons.share_outlined), | ||||
|                                   title: Text(L10n.of(context).inviteContact), | ||||
|                                   onTap: () { | ||||
|                                     Navigator.of(context).pop(); | ||||
|                                     FluffyShare.share( | ||||
|                                         L10n.of(context).inviteText( | ||||
|                                             Matrix.of(context).client.userID, | ||||
|                                             'https://matrix.to/#/${Matrix.of(context).client.userID}'), | ||||
|                                         context); | ||||
|                                   }, | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ), | ||||
|                         ), | ||||
|                   drawer: | ||||
|                       selectMode != SelectMode.normal ? null : DefaultDrawer(), | ||||
|                   appBar: AppBar( | ||||
|                     centerTitle: false, | ||||
|                     elevation: _scrolledToTop ? 0 : null, | ||||
| @ -387,39 +243,9 @@ class _ChatListState extends State<ChatList> { | ||||
|                         ? Text(L10n.of(context).share) | ||||
|                         : selectMode == SelectMode.select | ||||
|                             ? Text(_selectedRoomIds.length.toString()) | ||||
|                             : Container( | ||||
|                                 height: 40, | ||||
|                                 padding: EdgeInsets.only(right: 8), | ||||
|                                 child: Material( | ||||
|                                   color: Theme.of(context).secondaryHeaderColor, | ||||
|                                   borderRadius: BorderRadius.circular(32), | ||||
|                                   child: TextField( | ||||
|                                     autocorrect: false, | ||||
|                                     controller: searchController, | ||||
|                                     focusNode: _searchFocusNode, | ||||
|                                     decoration: InputDecoration( | ||||
|                                       contentPadding: EdgeInsets.only( | ||||
|                                         top: 8, | ||||
|                                         bottom: 8, | ||||
|                                         left: 16, | ||||
|                                       ), | ||||
|                                       border: OutlineInputBorder( | ||||
|                                         borderRadius: BorderRadius.circular(32), | ||||
|                                       ), | ||||
|                                       hintText: L10n.of(context).searchForAChat, | ||||
|                                       suffixIcon: searchMode | ||||
|                                           ? IconButton( | ||||
|                                               icon: Icon( | ||||
|                                                   Icons.backspace_outlined), | ||||
|                                               onPressed: () => setState(() { | ||||
|                                                 searchController.clear(); | ||||
|                                                 _searchFocusNode.unfocus(); | ||||
|                                               }), | ||||
|                                             ) | ||||
|                                           : null, | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                             : DefaultAppBarSearchField( | ||||
|                                 searchController: searchController, | ||||
|                                 onChanged: (_) => setState(() => null), | ||||
|                               ), | ||||
|                   ), | ||||
|                   floatingActionButton: AdaptivePageLayout.columnMode(context) | ||||
| @ -463,9 +289,7 @@ class _ChatListState extends State<ChatList> { | ||||
|                                                 .contains(searchController.text | ||||
|                                                         .toLowerCase() ?? | ||||
|                                                     ''))); | ||||
|                                     if (rooms.isEmpty && | ||||
|                                         (!searchMode || | ||||
|                                             publicRoomsResponse == null)) { | ||||
|                                     if (rooms.isEmpty && (!searchMode)) { | ||||
|                                       return Center( | ||||
|                                         child: Column( | ||||
|                                           mainAxisSize: MainAxisSize.min, | ||||
| @ -485,16 +309,12 @@ class _ChatListState extends State<ChatList> { | ||||
|                                         ), | ||||
|                                       ); | ||||
|                                     } | ||||
|                                     final publicRoomsCount = | ||||
|                                         (publicRoomsResponse?.chunk?.length ?? | ||||
|                                             0); | ||||
|                                     final totalCount = | ||||
|                                         rooms.length + publicRoomsCount; | ||||
|                                     final totalCount = rooms.length; | ||||
|                                     return ListView.separated( | ||||
|                                       controller: _scrollController, | ||||
|                                       separatorBuilder: (BuildContext context, | ||||
|                                               int i) => | ||||
|                                           i == totalCount - publicRoomsCount | ||||
|                                           i == totalCount | ||||
|                                               ? ListTile( | ||||
|                                                   title: Text( | ||||
|                                                     L10n.of(context) | ||||
| @ -510,31 +330,24 @@ class _ChatListState extends State<ChatList> { | ||||
|                                                 ) | ||||
|                                               : Container(), | ||||
|                                       itemCount: totalCount, | ||||
|                                       itemBuilder: (BuildContext context, | ||||
|                                               int i) => | ||||
|                                           i < rooms.length | ||||
|                                               ? ChatListItem( | ||||
|                                                   rooms[i], | ||||
|                                                   selected: _selectedRoomIds | ||||
|                                                       .contains(rooms[i].id), | ||||
|                                                   onTap: selectMode == | ||||
|                                                           SelectMode.select | ||||
|                                                       ? () => _toggleSelection( | ||||
|                                                           rooms[i].id) | ||||
|                                                       : null, | ||||
|                                                   onLongPress: selectMode != | ||||
|                                                           SelectMode.share | ||||
|                                                       ? () => _toggleSelection( | ||||
|                                                           rooms[i].id) | ||||
|                                                       : null, | ||||
|                                                   activeChat: | ||||
|                                                       widget.activeChat == | ||||
|                                                           rooms[i].id, | ||||
|                                                 ) | ||||
|                                               : PublicRoomListItem( | ||||
|                                                   publicRoomsResponse | ||||
|                                                       .chunk[i - rooms.length], | ||||
|                                                 ), | ||||
|                                       itemBuilder: | ||||
|                                           (BuildContext context, int i) => | ||||
|                                               ChatListItem( | ||||
|                                         rooms[i], | ||||
|                                         selected: _selectedRoomIds | ||||
|                                             .contains(rooms[i].id), | ||||
|                                         onTap: selectMode == SelectMode.select | ||||
|                                             ? () => | ||||
|                                                 _toggleSelection(rooms[i].id) | ||||
|                                             : null, | ||||
|                                         onLongPress: selectMode != | ||||
|                                                 SelectMode.share | ||||
|                                             ? () => | ||||
|                                                 _toggleSelection(rooms[i].id) | ||||
|                                             : null, | ||||
|                                         activeChat: | ||||
|                                             widget.activeChat == rooms[i].id, | ||||
|                                       ), | ||||
|                                     ); | ||||
|                                   } else { | ||||
|                                     return Center( | ||||
|  | ||||
							
								
								
									
										212
									
								
								lib/views/discover_view.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								lib/views/discover_view.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,212 @@ | ||||
| import 'dart:async'; | ||||
| 
 | ||||
| import 'package:adaptive_dialog/adaptive_dialog.dart'; | ||||
| import 'package:famedlysdk/famedlysdk.dart'; | ||||
| import 'package:fluffychat/components/adaptive_page_layout.dart'; | ||||
| import 'package:fluffychat/components/avatar.dart'; | ||||
| import 'package:fluffychat/components/default_app_bar_search_field.dart'; | ||||
| import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; | ||||
| import 'package:fluffychat/components/matrix.dart'; | ||||
| import 'package:fluffychat/utils/app_route.dart'; | ||||
| import 'package:fluffychat/views/chat.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/l10n.dart'; | ||||
| 
 | ||||
| import 'empty_page.dart'; | ||||
| 
 | ||||
| class DiscoverView extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return AdaptivePageLayout( | ||||
|       firstScaffold: DiscoverPage(), | ||||
|       secondScaffold: EmptyPage(), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class DiscoverPage extends StatefulWidget { | ||||
|   @override | ||||
|   _DiscoverPageState createState() => _DiscoverPageState(); | ||||
| } | ||||
| 
 | ||||
| class _DiscoverPageState extends State<DiscoverPage> { | ||||
|   final ScrollController _scrollController = ScrollController(); | ||||
|   bool _scrolledToTop = true; | ||||
|   Future<PublicRoomsResponse> _publicRoomsResponse; | ||||
|   Timer _coolDown; | ||||
|   String _server; | ||||
|   String _genericSearchTerm; | ||||
| 
 | ||||
|   void _search(BuildContext context, String query) async { | ||||
|     _coolDown?.cancel(); | ||||
|     _coolDown = Timer( | ||||
|       Duration(milliseconds: 500), | ||||
|       () => setState(() { | ||||
|         _genericSearchTerm = query; | ||||
|         _publicRoomsResponse = null; | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void _setServer(BuildContext context) async { | ||||
|     final newServer = await showTextInputDialog( | ||||
|         title: L10n.of(context).changeTheHomeserver, | ||||
|         context: context, | ||||
|         textFields: [ | ||||
|           DialogTextField( | ||||
|             hintText: Matrix.of(context).client.homeserver.toString(), | ||||
|             initialText: _server, | ||||
|             keyboardType: TextInputType.url, | ||||
|           ) | ||||
|         ]); | ||||
|     if (newServer == null) return; | ||||
|     setState(() { | ||||
|       _server = newServer.single; | ||||
|       _publicRoomsResponse = null; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   Future<String> _joinRoomAndWait(BuildContext context, String roomId) async { | ||||
|     final newRoomId = await Matrix.of(context).client.joinRoomOrAlias(roomId); | ||||
|     if (Matrix.of(context).client.getRoomById(newRoomId) == null) { | ||||
|       await Matrix.of(context) | ||||
|           .client | ||||
|           .onRoomUpdate | ||||
|           .stream | ||||
|           .firstWhere((r) => r.id == newRoomId); | ||||
|     } | ||||
|     return newRoomId; | ||||
|   } | ||||
| 
 | ||||
|   void _joinGroupAction(BuildContext context, PublicRoom room) async { | ||||
|     if (await showOkCancelAlertDialog( | ||||
|           context: context, | ||||
|           okLabel: L10n.of(context).joinRoom, | ||||
|           title: '${room.name} (${room.numJoinedMembers ?? 0})', | ||||
|           message: room.topic ?? L10n.of(context).noDescription, | ||||
|         ) == | ||||
|         OkCancelResult.cancel) { | ||||
|       return; | ||||
|     } | ||||
|     final success = await SimpleDialogs(context) | ||||
|         .tryRequestWithLoadingDialog(_joinRoomAndWait(context, room.roomId)); | ||||
|     if (success != false) { | ||||
|       await Navigator.of(context).push( | ||||
|         AppRoute.defaultRoute( | ||||
|           context, | ||||
|           ChatView(success), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     _scrollController.addListener(() async { | ||||
|       if (_scrollController.position.pixels > 0 && _scrolledToTop) { | ||||
|         setState(() => _scrolledToTop = false); | ||||
|       } else if (_scrollController.position.pixels == 0 && !_scrolledToTop) { | ||||
|         setState(() => _scrolledToTop = true); | ||||
|       } | ||||
|     }); | ||||
|     super.initState(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     _publicRoomsResponse ??= Matrix.of(context).client.searchPublicRooms( | ||||
|           server: _server, | ||||
|           genericSearchTerm: _genericSearchTerm, | ||||
|         ); | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         titleSpacing: 0, | ||||
|         elevation: _scrolledToTop ? 0 : null, | ||||
|         title: DefaultAppBarSearchField( | ||||
|           onChanged: (text) => _search(context, text), | ||||
|           suffix: IconButton( | ||||
|             icon: Icon(Icons.edit_outlined), | ||||
|             onPressed: () => _setServer(context), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       body: FutureBuilder<PublicRoomsResponse>( | ||||
|         future: _publicRoomsResponse, | ||||
|         builder: (BuildContext context, | ||||
|             AsyncSnapshot<PublicRoomsResponse> snapshot) { | ||||
|           if (snapshot.hasError) { | ||||
|             return Center(child: Text(snapshot.error.toString())); | ||||
|           } | ||||
|           if (!snapshot.hasData) { | ||||
|             return Center(child: CircularProgressIndicator()); | ||||
|           } | ||||
|           final publicRoomsResponse = snapshot.data; | ||||
|           if (publicRoomsResponse.chunk.isEmpty) { | ||||
|             return Center( | ||||
|               child: Text( | ||||
|                 'No public groups found...', | ||||
|                 textAlign: TextAlign.center, | ||||
|               ), | ||||
|             ); | ||||
|           } | ||||
|           return GridView.builder( | ||||
|             padding: EdgeInsets.all(16), | ||||
|             gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( | ||||
|               crossAxisCount: 2, | ||||
|               childAspectRatio: 1, | ||||
|               crossAxisSpacing: 16, | ||||
|               mainAxisSpacing: 16, | ||||
|             ), | ||||
|             controller: _scrollController, | ||||
|             itemCount: publicRoomsResponse.chunk.length, | ||||
|             itemBuilder: (BuildContext context, int i) => Material( | ||||
|               elevation: 2, | ||||
|               borderRadius: BorderRadius.circular(16), | ||||
|               child: InkWell( | ||||
|                 onTap: () => _joinGroupAction( | ||||
|                   context, | ||||
|                   publicRoomsResponse.chunk[i], | ||||
|                 ), | ||||
|                 borderRadius: BorderRadius.circular(16), | ||||
|                 child: Padding( | ||||
|                   padding: const EdgeInsets.all(8.0), | ||||
|                   child: Column( | ||||
|                     mainAxisSize: MainAxisSize.min, | ||||
|                     children: [ | ||||
|                       Avatar( | ||||
|                           Uri.parse( | ||||
|                               publicRoomsResponse.chunk[i].avatarUrl ?? ''), | ||||
|                           publicRoomsResponse.chunk[i].name), | ||||
|                       Text( | ||||
|                         publicRoomsResponse.chunk[i].name, | ||||
|                         style: TextStyle( | ||||
|                           fontSize: 16, | ||||
|                           fontWeight: FontWeight.bold, | ||||
|                         ), | ||||
|                         maxLines: 1, | ||||
|                         textAlign: TextAlign.center, | ||||
|                       ), | ||||
|                       Text( | ||||
|                         L10n.of(context).countParticipants( | ||||
|                             publicRoomsResponse.chunk[i].numJoinedMembers ?? 0), | ||||
|                         style: TextStyle(fontSize: 10.5), | ||||
|                         maxLines: 1, | ||||
|                         textAlign: TextAlign.center, | ||||
|                       ), | ||||
|                       Text( | ||||
|                         publicRoomsResponse.chunk[i].topic ?? | ||||
|                             L10n.of(context).noDescription, | ||||
|                         maxLines: 4, | ||||
|                         textAlign: TextAlign.center, | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								lib/views/empty_page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								lib/views/empty_page.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| class EmptyPage extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       body: Center( | ||||
|         child: Image.asset('assets/logo.png', width: 100, height: 100), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Christian Pauly
						Christian Pauly