/lib/pages/search/search_view.dart

https://gitlab.com/DerMolly/fluffychat · Dart · 299 lines · 291 code · 6 blank · 2 comment · 13 complexity · 0f5187a60ed029a76749ce482ccc5031 MD5 · raw file

  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_gen/gen_l10n/l10n.dart';
  3. import 'package:future_loading_dialog/future_loading_dialog.dart';
  4. import 'package:matrix/matrix.dart';
  5. import 'package:vrouter/vrouter.dart';
  6. import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
  7. import 'package:fluffychat/widgets/avatar.dart';
  8. import 'package:fluffychat/widgets/contacts_list.dart';
  9. import 'package:fluffychat/widgets/default_app_bar_search_field.dart';
  10. import 'package:fluffychat/widgets/matrix.dart';
  11. import '../../utils/localized_exception_extension.dart';
  12. import '../../utils/platform_infos.dart';
  13. import 'search.dart';
  14. class SearchView extends StatelessWidget {
  15. final SearchController controller;
  16. const SearchView(this.controller, {Key? key}) : super(key: key);
  17. @override
  18. Widget build(BuildContext context) {
  19. final server = controller.genericSearchTerm?.isValidMatrixId ?? false
  20. ? controller.genericSearchTerm!.domain
  21. : controller.server;
  22. if (controller.lastServer != server) {
  23. controller.lastServer = server;
  24. controller.publicRoomsResponse = null;
  25. }
  26. controller.publicRoomsResponse ??= Matrix.of(context)
  27. .client
  28. .queryPublicRooms(
  29. server: server,
  30. filter: PublicRoomQueryFilter(
  31. genericSearchTerm: controller.genericSearchTerm,
  32. ),
  33. )
  34. .catchError((error) {
  35. if (!(controller.genericSearchTerm?.isValidMatrixId ?? false)) {
  36. throw error;
  37. }
  38. return QueryPublicRoomsResponse.fromJson({
  39. 'chunk': [],
  40. });
  41. }).then((QueryPublicRoomsResponse res) {
  42. final genericSearchTerm = controller.genericSearchTerm;
  43. if (genericSearchTerm != null &&
  44. !res.chunk.any((room) =>
  45. (room.aliases?.contains(controller.genericSearchTerm) ?? false) ||
  46. room.canonicalAlias == controller.genericSearchTerm)) {
  47. // we have to tack on the original alias
  48. res.chunk.add(
  49. PublicRoomsChunk(
  50. aliases: [genericSearchTerm],
  51. name: genericSearchTerm,
  52. numJoinedMembers: 0,
  53. roomId: '!unknown',
  54. worldReadable: true,
  55. guestCanJoin: true,
  56. ),
  57. );
  58. }
  59. return res;
  60. });
  61. final rooms = List<Room>.from(Matrix.of(context).client.rooms);
  62. rooms.removeWhere(
  63. (room) =>
  64. room.lastEvent == null ||
  65. !room.displayname
  66. .toLowerCase()
  67. .contains(controller.controller.text.toLowerCase()),
  68. );
  69. const tabCount = 3;
  70. return DefaultTabController(
  71. length: tabCount,
  72. initialIndex: controller.controller.text.startsWith('#') ? 0 : 1,
  73. child: Scaffold(
  74. appBar: AppBar(
  75. leading: const BackButton(),
  76. titleSpacing: 0,
  77. title: DefaultAppBarSearchField(
  78. autofocus: true,
  79. hintText: L10n.of(context)!.search,
  80. searchController: controller.controller,
  81. suffix: const Icon(Icons.search_outlined),
  82. onChanged: controller.search,
  83. ),
  84. bottom: TabBar(
  85. indicatorColor: Theme.of(context).colorScheme.secondary,
  86. labelColor: Theme.of(context).colorScheme.secondary,
  87. unselectedLabelColor: Theme.of(context).textTheme.bodyText1!.color,
  88. labelStyle: const TextStyle(fontSize: 16),
  89. labelPadding: const EdgeInsets.symmetric(
  90. horizontal: 8,
  91. vertical: 0,
  92. ),
  93. tabs: [
  94. Tab(child: Text(L10n.of(context)!.discover, maxLines: 1)),
  95. Tab(child: Text(L10n.of(context)!.chats, maxLines: 1)),
  96. Tab(child: Text(L10n.of(context)!.people, maxLines: 1)),
  97. ],
  98. ),
  99. ),
  100. body: TabBarView(
  101. children: [
  102. ListView(
  103. keyboardDismissBehavior: PlatformInfos.isIOS
  104. ? ScrollViewKeyboardDismissBehavior.onDrag
  105. : ScrollViewKeyboardDismissBehavior.manual,
  106. children: [
  107. const SizedBox(height: 12),
  108. ListTile(
  109. leading: CircleAvatar(
  110. foregroundColor: Theme.of(context).colorScheme.secondary,
  111. backgroundColor: Theme.of(context).secondaryHeaderColor,
  112. child: const Icon(Icons.edit_outlined),
  113. ),
  114. title: Text(L10n.of(context)!.changeTheServer),
  115. onTap: controller.setServer,
  116. ),
  117. FutureBuilder<QueryPublicRoomsResponse>(
  118. future: controller.publicRoomsResponse,
  119. builder: (BuildContext context,
  120. AsyncSnapshot<QueryPublicRoomsResponse> snapshot) {
  121. if (snapshot.hasError) {
  122. return Column(
  123. mainAxisSize: MainAxisSize.min,
  124. children: [
  125. const SizedBox(height: 32),
  126. const Icon(
  127. Icons.error_outlined,
  128. size: 80,
  129. color: Colors.grey,
  130. ),
  131. Center(
  132. child: Text(
  133. snapshot.error!.toLocalizedString(context),
  134. textAlign: TextAlign.center,
  135. style: const TextStyle(
  136. color: Colors.grey,
  137. fontSize: 16,
  138. ),
  139. ),
  140. ),
  141. ],
  142. );
  143. }
  144. if (snapshot.connectionState != ConnectionState.done) {
  145. return const Center(
  146. child: CircularProgressIndicator.adaptive(
  147. strokeWidth: 2));
  148. }
  149. final publicRoomsResponse = snapshot.data!;
  150. if (publicRoomsResponse.chunk.isEmpty) {
  151. return Column(
  152. mainAxisSize: MainAxisSize.min,
  153. children: [
  154. const SizedBox(height: 32),
  155. const Icon(
  156. Icons.search_outlined,
  157. size: 80,
  158. color: Colors.grey,
  159. ),
  160. Center(
  161. child: Text(
  162. L10n.of(context)!.noPublicRoomsFound,
  163. textAlign: TextAlign.center,
  164. style: const TextStyle(
  165. color: Colors.grey,
  166. fontSize: 16,
  167. ),
  168. ),
  169. ),
  170. ],
  171. );
  172. }
  173. return GridView.builder(
  174. shrinkWrap: true,
  175. padding: const EdgeInsets.all(12),
  176. physics: const NeverScrollableScrollPhysics(),
  177. gridDelegate:
  178. const SliverGridDelegateWithFixedCrossAxisCount(
  179. crossAxisCount: 2,
  180. childAspectRatio: 1,
  181. crossAxisSpacing: 16,
  182. mainAxisSpacing: 16,
  183. ),
  184. itemCount: publicRoomsResponse.chunk.length,
  185. itemBuilder: (BuildContext context, int i) => Material(
  186. elevation: 2,
  187. borderRadius: BorderRadius.circular(16),
  188. child: InkWell(
  189. onTap: () => controller.joinGroupAction(
  190. publicRoomsResponse.chunk[i],
  191. ),
  192. borderRadius: BorderRadius.circular(16),
  193. child: Padding(
  194. padding: const EdgeInsets.all(8.0),
  195. child: Column(
  196. mainAxisSize: MainAxisSize.min,
  197. children: [
  198. Avatar(
  199. mxContent:
  200. publicRoomsResponse.chunk[i].avatarUrl,
  201. name: publicRoomsResponse.chunk[i].name,
  202. ),
  203. Text(
  204. publicRoomsResponse.chunk[i].name!,
  205. style: const TextStyle(
  206. fontSize: 16,
  207. fontWeight: FontWeight.bold,
  208. ),
  209. maxLines: 1,
  210. textAlign: TextAlign.center,
  211. ),
  212. Text(
  213. L10n.of(context)!.countParticipants(
  214. publicRoomsResponse
  215. .chunk[i].numJoinedMembers),
  216. style: const TextStyle(fontSize: 10.5),
  217. maxLines: 1,
  218. textAlign: TextAlign.center,
  219. ),
  220. Text(
  221. publicRoomsResponse.chunk[i].topic ??
  222. L10n.of(context)!.noDescription,
  223. maxLines: 4,
  224. textAlign: TextAlign.center,
  225. ),
  226. ],
  227. ),
  228. ),
  229. ),
  230. ),
  231. );
  232. }),
  233. ],
  234. ),
  235. ListView.builder(
  236. keyboardDismissBehavior: PlatformInfos.isIOS
  237. ? ScrollViewKeyboardDismissBehavior.onDrag
  238. : ScrollViewKeyboardDismissBehavior.manual,
  239. itemCount: rooms.length,
  240. itemBuilder: (_, i) => ChatListItem(rooms[i]),
  241. ),
  242. controller.foundProfiles.isNotEmpty
  243. ? ListView.builder(
  244. keyboardDismissBehavior: PlatformInfos.isIOS
  245. ? ScrollViewKeyboardDismissBehavior.onDrag
  246. : ScrollViewKeyboardDismissBehavior.manual,
  247. itemCount: controller.foundProfiles.length,
  248. itemBuilder: (BuildContext context, int i) {
  249. final foundProfile = controller.foundProfiles[i];
  250. return ListTile(
  251. onTap: () async {
  252. final roomID = await showFutureLoadingDialog(
  253. context: context,
  254. future: () async {
  255. final client = Matrix.of(context).client;
  256. final roomId = await client
  257. .startDirectChat(foundProfile.userId);
  258. return roomId;
  259. },
  260. );
  261. if (roomID.error == null) {
  262. VRouter.of(context)
  263. .toSegments(['rooms', roomID.result!]);
  264. }
  265. },
  266. leading: Avatar(
  267. mxContent: foundProfile.avatarUrl,
  268. name: foundProfile.displayName ?? foundProfile.userId,
  269. //size: 24,
  270. ),
  271. title: Text(
  272. foundProfile.displayName ??
  273. foundProfile.userId.localpart!,
  274. style: const TextStyle(),
  275. maxLines: 1,
  276. ),
  277. subtitle: Text(
  278. foundProfile.userId,
  279. maxLines: 1,
  280. style: const TextStyle(
  281. fontSize: 12,
  282. ),
  283. ),
  284. );
  285. },
  286. )
  287. : ContactsList(searchController: controller.controller),
  288. ],
  289. ),
  290. ),
  291. );
  292. }
  293. }