mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-10-31 03:57:27 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			303 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:async';
 | |
| 
 | |
| import 'package:adaptive_dialog/adaptive_dialog.dart';
 | |
| import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | |
| import 'package:famedlysdk/famedlysdk.dart';
 | |
| import 'package:fluffychat/components/avatar.dart';
 | |
| import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | |
| import 'package:fluffychat/components/default_bottom_navigation_bar.dart';
 | |
| import 'package:future_loading_dialog/future_loading_dialog.dart';
 | |
| import 'package:fluffychat/components/matrix.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter_gen/gen_l10n/l10n.dart';
 | |
| import '../utils/localized_exception_extension.dart';
 | |
| 
 | |
| class Discover extends StatefulWidget {
 | |
|   final String alias;
 | |
| 
 | |
|   const Discover({
 | |
|     Key key,
 | |
|     this.alias,
 | |
|   }) : super(key: key);
 | |
|   @override
 | |
|   _DiscoverState createState() => _DiscoverState();
 | |
| }
 | |
| 
 | |
| class _DiscoverState extends State<Discover> {
 | |
|   Future<PublicRoomsResponse> _publicRoomsResponse;
 | |
|   String _lastServer;
 | |
|   Timer _coolDown;
 | |
|   String _genericSearchTerm;
 | |
| 
 | |
|   void _search(BuildContext context, String query) async {
 | |
|     _coolDown?.cancel();
 | |
|     _coolDown = Timer(
 | |
|       Duration(milliseconds: 500),
 | |
|       () => setState(() {
 | |
|         _genericSearchTerm = query;
 | |
|         _publicRoomsResponse = null;
 | |
|       }),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Future<String> _joinRoomAndWait(
 | |
|     BuildContext context,
 | |
|     String roomId,
 | |
|     String alias,
 | |
|   ) async {
 | |
|     if (Matrix.of(context).client.getRoomById(roomId) != null) {
 | |
|       return roomId;
 | |
|     }
 | |
|     final newRoomId = await Matrix.of(context)
 | |
|         .client
 | |
|         .joinRoomOrAlias(alias?.isNotEmpty ?? false ? alias : roomId);
 | |
|     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,
 | |
|           cancelLabel: L10n.of(context).cancel,
 | |
|           useRootNavigator: false,
 | |
|         ) ==
 | |
|         OkCancelResult.cancel) {
 | |
|       return;
 | |
|     }
 | |
|     final success = await showFutureLoadingDialog(
 | |
|       context: context,
 | |
|       future: () => _joinRoomAndWait(
 | |
|         context,
 | |
|         room.roomId,
 | |
|         room.canonicalAlias ?? room.aliases.first,
 | |
|       ),
 | |
|     );
 | |
|     if (success.error == null) {
 | |
|       await AdaptivePageLayout.of(context)
 | |
|           .pushNamedAndRemoveUntilIsFirst('/rooms/${success.result}');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   String _server;
 | |
| 
 | |
|   void _setServer(BuildContext context) async {
 | |
|     final newServer = await showTextInputDialog(
 | |
|         title: L10n.of(context).changeTheHomeserver,
 | |
|         context: context,
 | |
|         okLabel: L10n.of(context).ok,
 | |
|         cancelLabel: L10n.of(context).cancel,
 | |
|         useRootNavigator: false,
 | |
|         textFields: [
 | |
|           DialogTextField(
 | |
|             prefixText: 'https://',
 | |
|             hintText: Matrix.of(context).client.homeserver.host,
 | |
|             initialText: _server,
 | |
|             keyboardType: TextInputType.url,
 | |
|           )
 | |
|         ]);
 | |
|     if (newServer == null) return;
 | |
|     setState(() {
 | |
|       _server = newServer.single;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     _genericSearchTerm = widget.alias;
 | |
|     super.initState();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     final server = _genericSearchTerm?.isValidMatrixId ?? false
 | |
|         ? _genericSearchTerm.domain
 | |
|         : _server;
 | |
|     if (_lastServer != server) {
 | |
|       _lastServer = server;
 | |
|       _publicRoomsResponse = null;
 | |
|     }
 | |
|     _publicRoomsResponse ??= Matrix.of(context)
 | |
|         .client
 | |
|         .searchPublicRooms(
 | |
|           server: server,
 | |
|           genericSearchTerm: _genericSearchTerm,
 | |
|         )
 | |
|         .catchError((error) {
 | |
|       if (widget.alias == null) {
 | |
|         throw error;
 | |
|       }
 | |
|       return PublicRoomsResponse.fromJson({
 | |
|         'chunk': [],
 | |
|       });
 | |
|     }).then((PublicRoomsResponse res) {
 | |
|       if (widget.alias != null &&
 | |
|           !res.chunk.any((room) =>
 | |
|               (room.aliases?.contains(widget.alias) ?? false) ||
 | |
|               room.canonicalAlias == widget.alias)) {
 | |
|         // we have to tack on the original alias
 | |
|         res.chunk.add(PublicRoom.fromJson(<String, dynamic>{
 | |
|           'aliases': [widget.alias],
 | |
|           'name': widget.alias,
 | |
|         }));
 | |
|       }
 | |
|       return res;
 | |
|     });
 | |
|     return Scaffold(
 | |
|       appBar: AppBar(
 | |
|         elevation: 1,
 | |
|         automaticallyImplyLeading: false,
 | |
|         title: Text(L10n.of(context).discoverGroups),
 | |
|         actions: [
 | |
|           TextButton.icon(
 | |
|             label: Text(
 | |
|               server ?? Matrix.of(context).client.userID.domain,
 | |
|               style: TextStyle(color: Theme.of(context).accentColor),
 | |
|             ),
 | |
|             icon: Icon(
 | |
|               Icons.edit_outlined,
 | |
|               color: Theme.of(context).accentColor,
 | |
|             ),
 | |
|             onPressed: () => _setServer(context),
 | |
|           ),
 | |
|         ],
 | |
|       ),
 | |
|       body: ListView(
 | |
|         children: [
 | |
|           Padding(
 | |
|             padding: EdgeInsets.all(12),
 | |
|             child: DefaultAppBarSearchField(
 | |
|               hintText: L10n.of(context).search,
 | |
|               prefixIcon: Icon(Icons.search_outlined),
 | |
|               onChanged: (t) => _search(context, t),
 | |
|               padding: EdgeInsets.zero,
 | |
|             ),
 | |
|           ),
 | |
|           FutureBuilder<PublicRoomsResponse>(
 | |
|               future: _publicRoomsResponse,
 | |
|               builder: (BuildContext context,
 | |
|                   AsyncSnapshot<PublicRoomsResponse> snapshot) {
 | |
|                 if (snapshot.hasError) {
 | |
|                   return Column(
 | |
|                     mainAxisSize: MainAxisSize.min,
 | |
|                     children: [
 | |
|                       SizedBox(height: 32),
 | |
|                       Icon(
 | |
|                         Icons.error_outlined,
 | |
|                         size: 80,
 | |
|                         color: Colors.grey,
 | |
|                       ),
 | |
|                       Center(
 | |
|                         child: Text(
 | |
|                           snapshot.error.toLocalizedString(context),
 | |
|                           textAlign: TextAlign.center,
 | |
|                           style: TextStyle(
 | |
|                             color: Colors.grey,
 | |
|                             fontSize: 16,
 | |
|                           ),
 | |
|                         ),
 | |
|                       ),
 | |
|                     ],
 | |
|                   );
 | |
|                 }
 | |
|                 if (snapshot.connectionState != ConnectionState.done) {
 | |
|                   return Center(child: CircularProgressIndicator());
 | |
|                 }
 | |
|                 final publicRoomsResponse = snapshot.data;
 | |
|                 if (publicRoomsResponse.chunk.isEmpty) {
 | |
|                   return Column(
 | |
|                     mainAxisSize: MainAxisSize.min,
 | |
|                     children: [
 | |
|                       SizedBox(height: 32),
 | |
|                       Icon(
 | |
|                         Icons.search_outlined,
 | |
|                         size: 80,
 | |
|                         color: Colors.grey,
 | |
|                       ),
 | |
|                       Center(
 | |
|                         child: Text(
 | |
|                           L10n.of(context).noPublicRoomsFound,
 | |
|                           textAlign: TextAlign.center,
 | |
|                           style: TextStyle(
 | |
|                             color: Colors.grey,
 | |
|                             fontSize: 16,
 | |
|                           ),
 | |
|                         ),
 | |
|                       ),
 | |
|                     ],
 | |
|                   );
 | |
|                 }
 | |
|                 return GridView.builder(
 | |
|                   shrinkWrap: true,
 | |
|                   padding: EdgeInsets.all(12),
 | |
|                   physics: NeverScrollableScrollPhysics(),
 | |
|                   gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 | |
|                     crossAxisCount: 2,
 | |
|                     childAspectRatio: 1,
 | |
|                     crossAxisSpacing: 16,
 | |
|                     mainAxisSpacing: 16,
 | |
|                   ),
 | |
|                   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,
 | |
|                             ),
 | |
|                           ],
 | |
|                         ),
 | |
|                       ),
 | |
|                     ),
 | |
|                   ),
 | |
|                 );
 | |
|               }),
 | |
|         ],
 | |
|       ),
 | |
|       bottomNavigationBar: DefaultBottomNavigationBar(currentIndex: 2),
 | |
|     );
 | |
|   }
 | |
| }
 | 
