From 091958be0b0a987f44eb0922c533d9dd5ed147b1 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 7 Jul 2022 18:50:13 +0200 Subject: [PATCH] feat: New material 3 design --- assets/l10n/intl_en.arb | 4 +- lib/config/routes.dart | 13 - lib/config/themes.dart | 101 +----- lib/pages/chat/chat_view.dart | 9 +- lib/pages/chat/events/message.dart | 4 +- lib/pages/chat_list/chat_list.dart | 143 ++++++--- lib/pages/chat_list/chat_list_body.dart | 191 ++++++++++- lib/pages/chat_list/chat_list_drawer.dart | 67 ++++ lib/pages/chat_list/chat_list_header.dart | 240 +++++--------- lib/pages/chat_list/chat_list_view.dart | 99 ++---- .../chat_list/client_chooser_button.dart | 17 +- lib/pages/chat_list/spaces_bottom_bar.dart | 164 ---------- lib/pages/chat_list/spaces_drawer.dart | 127 +++++--- lib/pages/chat_list/stories_header.dart | 15 +- lib/pages/connect/connect_page_view.dart | 30 +- lib/pages/connect/sso_button.dart | 6 +- .../device_settings/device_settings_view.dart | 99 +++--- .../user_device_list_item.dart | 11 +- .../homeserver_picker_view.dart | 225 +++++++------ lib/pages/login/login_view.dart | 36 +-- lib/pages/search/search.dart | 125 -------- lib/pages/search/search_view.dart | 299 ------------------ lib/pages/settings/settings_view.dart | 2 +- lib/pages/sign_up/signup_view.dart | 49 +-- macos/Podfile.lock | 38 +-- macos/Runner.xcodeproj/project.pbxproj | 10 +- pubspec.lock | 7 - pubspec.yaml | 1 - 28 files changed, 821 insertions(+), 1311 deletions(-) create mode 100644 lib/pages/chat_list/chat_list_drawer.dart delete mode 100644 lib/pages/chat_list/spaces_bottom_bar.dart delete mode 100644 lib/pages/search/search.dart delete mode 100644 lib/pages/search/search_view.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index b0741688..74168531 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2826,5 +2826,7 @@ "user": {} } }, - "noEmailWarning": "Please enter a valid email address. Otherwise you won't be able to reset your password. If you don't want to, tap again on the button to continue." + "noEmailWarning": "Please enter a valid email address. Otherwise you won't be able to reset your password. If you don't want to, tap again on the button to continue.", + "stories": "Stories", + "users": "Users" } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index cc9cd2e1..6eb061a6 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -17,7 +17,6 @@ import 'package:fluffychat/pages/login/login.dart'; import 'package:fluffychat/pages/new_group/new_group.dart'; import 'package:fluffychat/pages/new_private_chat/new_private_chat.dart'; import 'package:fluffychat/pages/new_space/new_space.dart'; -import 'package:fluffychat/pages/search/search.dart'; import 'package:fluffychat/pages/settings/settings.dart'; import 'package:fluffychat/pages/settings_3pid/settings_3pid.dart'; import 'package:fluffychat/pages/settings_account/settings_account.dart'; @@ -92,10 +91,6 @@ class AppRoutes { widget: const Settings(), stackedRoutes: _settingsRoutes, ), - VWidget( - path: '/search', - widget: const Search(), - ), VWidget( path: '/archive', widget: const Archive(), @@ -225,14 +220,6 @@ class AppRoutes { ), ], ), - VWidget( - path: '/search', - widget: const TwoColumnLayout( - mainView: Search(), - sideView: EmptyPage(), - ), - buildTransition: _fadeTransition, - ), VWidget( path: '/archive', widget: const TwoColumnLayout( diff --git a/lib/config/themes.dart b/lib/config/themes.dart index 9f4ca2c3..9ca9f686 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -9,56 +9,10 @@ abstract class FluffyThemes { static bool isColumnMode(BuildContext context) => MediaQuery.of(context).size.width > columnWidth * 2; - static const fallbackTextStyle = - TextStyle(fontFamily: 'Roboto', fontFamilyFallback: ['NotoEmoji']); - - static const TextStyle loginTextFieldStyle = TextStyle(color: Colors.black); - - static InputDecoration loginTextFieldDecoration({ - String? errorText, - String? labelText, - String? hintText, - Widget? suffixIcon, - Widget? prefixIcon, - Color? errorColor, - }) => - InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - ), - fillColor: Colors.white.withAlpha(200), - labelText: labelText, - hintText: hintText, - suffixIcon: suffixIcon, - prefixIcon: prefixIcon, - suffixIconColor: Colors.black, - prefixIconColor: Colors.black, - iconColor: Colors.black, - errorText: errorText, - errorMaxLines: 4, - errorStyle: TextStyle( - color: errorColor ?? Colors.redAccent.shade200, - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 10, - ), - ], - ), - hintStyle: TextStyle(color: Colors.grey.shade700), - labelStyle: const TextStyle( - color: Colors.white, - shadows: [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 5, - ), - ], - ), - contentPadding: const EdgeInsets.all(16), - ); + static const fallbackTextStyle = TextStyle( + fontFamily: 'Roboto', + fontFamilyFallback: ['NotoEmoji'], + ); static var fallbackTextTheme = const TextTheme( bodyText1: fallbackTextStyle, @@ -83,12 +37,12 @@ abstract class FluffyThemes { colorSchemeSeed: AppConfig.colorSchemeSeed ?? colorScheme?.primary ?? AppConfig.chatColor, - scaffoldBackgroundColor: Colors.white, textTheme: PlatformInfos.isDesktop ? Typography.material2018().black.merge(fallbackTextTheme) : null, - snackBarTheme: - const SnackBarThemeData(behavior: SnackBarBehavior.floating), + snackBarTheme: const SnackBarThemeData( + behavior: SnackBarBehavior.floating, + ), pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(), @@ -100,31 +54,12 @@ abstract class FluffyThemes { }, ), dividerColor: Colors.blueGrey.shade50, - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - textStyle: const TextStyle(fontSize: 16), - elevation: 6, - shadowColor: const Color(0x44000000), - minimumSize: const Size.fromHeight(48), - padding: const EdgeInsets.all(12), - ), - ), - cardTheme: const CardTheme( - elevation: 6, - // shadowColor: Color(0x44000000), - clipBehavior: Clip.hardEdge, - ), - inputDecorationTheme: InputDecorationTheme( - border: const UnderlineInputBorder(borderSide: BorderSide(width: 1)), + inputDecorationTheme: const InputDecorationTheme( + border: UnderlineInputBorder(borderSide: BorderSide(width: 1)), filled: true, - fillColor: Colors.blueGrey.shade50, ), appBarTheme: const AppBarTheme( - elevation: 6, - shadowColor: Color(0x44000000), systemOverlayStyle: SystemUiOverlayStyle.dark, - surfaceTintColor: Colors.white, - backgroundColor: Colors.white, ), ); @@ -135,7 +70,6 @@ abstract class FluffyThemes { colorSchemeSeed: AppConfig.colorSchemeSeed ?? colorScheme?.primary ?? AppConfig.chatColor, - scaffoldBackgroundColor: Colors.black, textTheme: PlatformInfos.isDesktop ? Typography.material2018().white.merge(fallbackTextTheme) : null, @@ -151,20 +85,11 @@ abstract class FluffyThemes { TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), }, ), - dividerColor: Colors.blueGrey.shade600, - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - primary: AppConfig.chatColor, - onPrimary: Colors.white, - minimumSize: const Size.fromHeight(48), - textStyle: const TextStyle(fontSize: 16), - padding: const EdgeInsets.all(12), - ), - ), - appBarTheme: const AppBarTheme( - elevation: 6, - backgroundColor: Color(0xff1D1D1D), + inputDecorationTheme: const InputDecorationTheme( + border: UnderlineInputBorder(borderSide: BorderSide(width: 1)), + filled: true, ), + dividerColor: Colors.blueGrey.shade900, ); static Color blackWhiteColor(BuildContext context) => diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 1fa6d1bd..69dcbe17 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -255,12 +255,13 @@ class ChatView extends StatelessWidget { ), elevation: 6, shadowColor: Theme.of(context) - .secondaryHeaderColor + .dividerColor .withAlpha(100), clipBehavior: Clip.hardEdge, - color: Theme.of(context) - .appBarTheme - .backgroundColor, + color: Theme.of(context).brightness == + Brightness.light + ? Colors.white + : Colors.black, child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index e0539799..605e8a09 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -68,7 +68,9 @@ class Message extends StatelessWidget { final client = Matrix.of(context).client; final ownMessage = event.senderId == client.userID; final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft; - var color = Theme.of(context).scaffoldBackgroundColor; + var color = Theme.of(context).brightness == Brightness.light + ? Colors.white + : Colors.black; final displayTime = event.type == EventTypes.RoomCreate || nextEvent == null || !event.originServerTs.sameEnvironment(nextEvent!.originServerTs); diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index bbe06ce2..8385a5ac 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -14,9 +14,9 @@ import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; -import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart'; import 'package:fluffychat/pages/chat_list/spaces_entry.dart'; -import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/utils/famedlysdk_store.dart'; +import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import '../../../utils/account_bundles.dart'; import '../../main.dart'; @@ -52,6 +52,97 @@ class ChatListController extends State with TickerProviderStateMixin { SpacesEntry? _activeSpacesEntry; + bool isSearchMode = false; + Future? publicRoomsResponse; + String? searchServer; + Timer? _coolDown; + SearchUserDirectoryResponse? userSearchResult; + QueryPublicRoomsResponse? roomSearchResult; + + bool isSearching = false; + static const String _serverStoreNamespace = 'im.fluffychat.search.server'; + + void setServer() async { + final newServer = await showTextInputDialog( + useRootNavigator: false, + title: L10n.of(context)!.changeTheHomeserver, + context: context, + okLabel: L10n.of(context)!.ok, + cancelLabel: L10n.of(context)!.cancel, + textFields: [ + DialogTextField( + prefixText: 'https://', + hintText: Matrix.of(context).client.homeserver?.host, + initialText: searchServer, + keyboardType: TextInputType.url, + autocorrect: false) + ]); + if (newServer == null) return; + Store().setItem(_serverStoreNamespace, newServer.single); + setState(() { + searchServer = newServer.single; + }); + onSearchEnter(searchController.text); + } + + final TextEditingController searchController = TextEditingController(); + + void _search() async { + final client = Matrix.of(context).client; + if (!isSearching) { + setState(() { + isSearching = true; + }); + } + SearchUserDirectoryResponse? userSearchResult; + QueryPublicRoomsResponse? roomSearchResult; + try { + roomSearchResult = await client.queryPublicRooms( + server: searchServer, + filter: PublicRoomQueryFilter(genericSearchTerm: searchController.text), + limit: 20, + ); + userSearchResult = await client.searchUserDirectory( + searchController.text, + limit: 20, + ); + } catch (e, s) { + Logs().w('Searching has crashed', e, s); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + e.toLocalizedString(context), + ), + ), + ); + } + setState(() { + isSearching = false; + this.roomSearchResult = roomSearchResult; + this.userSearchResult = userSearchResult; + }); + } + + void onSearchEnter(String text) { + if (text.isEmpty) { + cancelSearch(); + return; + } + + setState(() { + isSearchMode = true; + }); + _coolDown?.cancel(); + _coolDown = Timer(const Duration(milliseconds: 500), _search); + } + + void cancelSearch() => setState(() { + searchController.clear(); + isSearchMode = false; + roomSearchResult = userSearchResult = null; + isSearching = false; + }); + SpacesEntry get activeSpacesEntry { final id = _activeSpacesEntry; return (id == null || !id.stillValid(context)) ? defaultSpacesEntry : id; @@ -72,6 +163,8 @@ class ChatListController extends State with TickerProviderStateMixin { Stream get clientStream => _clientStream.stream; + void addAccountAction() => VRouter.of(context).to('/settings/account/add'); + void _onScroll() { final newScrolledToTop = scrollController.position.pixels <= 0; if (newScrolledToTop != scrolledToTop) { @@ -82,12 +175,7 @@ class ChatListController extends State with TickerProviderStateMixin { } void setActiveSpacesEntry(BuildContext context, SpacesEntry? spaceId) { - if ((snappingSheetController.isAttached - ? snappingSheetController.currentPosition - : 0) != - kSpacesBottomBarHeight) { - snapBackSpacesSheet(); - } + Scaffold.of(context).closeDrawer(); setState(() => _activeSpacesEntry = spaceId); } @@ -212,6 +300,10 @@ class ChatListController extends State with TickerProviderStateMixin { scrollController.addListener(_onScroll); _waitForFirstSync(); _hackyWebRTCFixForWeb(); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + searchServer = await Store().getItem(_serverStoreNamespace); + }); super.initState(); } @@ -338,32 +430,6 @@ class ChatListController extends State with TickerProviderStateMixin { ); } - void onPopupMenuSelect(action) { - switch (action) { - case PopupMenuAction.setStatus: - setStatus(); - break; - case PopupMenuAction.settings: - VRouter.of(context).to('/settings'); - break; - case PopupMenuAction.invite: - FluffyShare.share( - L10n.of(context)!.inviteText(Matrix.of(context).client.userID!, - 'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'), - context); - break; - case PopupMenuAction.newGroup: - VRouter.of(context).to('/newgroup'); - break; - case PopupMenuAction.newSpace: - VRouter.of(context).to('/newspace'); - break; - case PopupMenuAction.archive: - VRouter.of(context).to('/archive'); - break; - } - } - Future _archiveSelectedRooms() async { final client = Matrix.of(context).client; while (selectedRoomIds.isNotEmpty) { @@ -593,15 +659,6 @@ class ChatListController extends State with TickerProviderStateMixin { Matrix.of(context).voipPlugin?.context = context; } - void snapBackSpacesSheet() { - snappingSheetController.snapToPosition( - const SnappingPosition.pixels( - positionPixels: kSpacesBottomBarHeight, - snappingDuration: Duration(milliseconds: 500), - ), - ); - } - expandSpaces() { snappingSheetController.snapToPosition( const SnappingPosition.factor(positionFactor: 0.5), diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 36300d17..77a0034f 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -8,9 +8,12 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; -import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart'; import 'package:fluffychat/pages/chat_list/spaces_entry.dart'; import 'package:fluffychat/pages/chat_list/stories_header.dart'; +import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/connection_status_header.dart'; +import 'package:fluffychat/widgets/profile_bottom_sheet.dart'; +import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; import '../../utils/stream_extension.dart'; import '../../widgets/matrix.dart'; @@ -46,6 +49,8 @@ class _ChatListViewBodyState extends State { @override Widget build(BuildContext context) { final reversed = !_animationReversed(); + final roomSearchResult = widget.controller.roomSearchResult; + final userSearchResult = widget.controller.userSearchResult; Widget child; if (widget.controller.waitForFirstSync && Matrix.of(context).client.prevBatch != null) { @@ -86,13 +91,113 @@ class _ChatListViewBodyState extends State { itemBuilder: (BuildContext context, int i) { if (displayStoriesHeader) { if (i == 0) { - return const StoriesHeader(); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ConnectionStatusHeader(), + if (roomSearchResult != null) ...[ + _SearchTitle(title: L10n.of(context)!.publicRooms), + AnimatedContainer( + height: roomSearchResult.chunk.isEmpty ? 0 : 106, + duration: const Duration(milliseconds: 250), + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: roomSearchResult.chunk.length, + itemBuilder: (context, i) => _SearchItem( + title: roomSearchResult.chunk[i].name ?? + roomSearchResult + .chunk[i].canonicalAlias?.localpart ?? + L10n.of(context)!.group, + avatar: roomSearchResult.chunk[i].avatarUrl, + onPressed: () => showModalBottomSheet( + context: context, + builder: (c) => PublicRoomBottomSheet( + roomAlias: + roomSearchResult.chunk[i].canonicalAlias ?? + roomSearchResult.chunk[i].roomId, + outerContext: context, + chunk: roomSearchResult.chunk[i], + ), + ), + ), + ), + ), + ], + if (userSearchResult != null) ...[ + _SearchTitle(title: L10n.of(context)!.users), + AnimatedContainer( + height: userSearchResult.results.isEmpty ? 0 : 106, + duration: const Duration(milliseconds: 250), + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: userSearchResult.results.length, + itemBuilder: (context, i) => _SearchItem( + title: userSearchResult.results[i].displayName ?? + userSearchResult.results[i].userId.localpart ?? + L10n.of(context)!.unknownDevice, + avatar: userSearchResult.results[i].avatarUrl, + onPressed: () => showModalBottomSheet( + context: context, + builder: (c) => ProfileBottomSheet( + userId: userSearchResult.results[i].userId, + outerContext: context, + ), + ), + ), + ), + ), + ], + if (widget.controller.isSearchMode) + _SearchTitle(title: L10n.of(context)!.stories), + StoriesHeader( + filter: widget.controller.searchController.text, + ), + AnimatedContainer( + height: !widget.controller.isSearchMode && + widget.controller.showChatBackupBanner + ? 54 + : 0, + duration: const Duration(milliseconds: 300), + clipBehavior: Clip.hardEdge, + curve: Curves.bounceInOut, + decoration: const BoxDecoration(), + child: Material( + color: Theme.of(context).colorScheme.surface, + child: ListTile( + leading: Image.asset( + 'assets/backup.png', + fit: BoxFit.contain, + width: 44, + ), + title: Text( + L10n.of(context)!.setupChatBackupNow, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + trailing: const Icon(Icons.chevron_right_outlined), + onTap: widget.controller.firstRunBootstrapAction, + ), + ), + ), + if (widget.controller.isSearchMode) + _SearchTitle(title: L10n.of(context)!.chats), + ], + ); } i--; } if (i >= rooms.length) { return const ListTile(); } + if (!rooms[i].displayname.toLowerCase().contains( + widget.controller.searchController.text.toLowerCase())) { + return Container(); + } return ChatListItem( rooms[i], selected: widget.controller.selectedRoomIds.contains(rooms[i].id), @@ -176,13 +281,7 @@ class _ChatListViewBodyState extends State { return SharedAxisTransition( animation: primaryAnimation, secondaryAnimation: secondaryAnimation, - transitionType: (widget.controller.snappingSheetController.isAttached - ? widget - .controller.snappingSheetController.currentPosition - : 0) == - kSpacesBottomBarHeight - ? SharedAxisTransitionType.horizontal - : SharedAxisTransitionType.vertical, + transitionType: SharedAxisTransitionType.vertical, fillColor: Theme.of(context).scaffoldBackgroundColor, child: child, ); @@ -221,3 +320,77 @@ class _ChatListViewBodyState extends State { return reversed; } } + +class _SearchTitle extends StatelessWidget { + final String title; + const _SearchTitle({required this.title, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => Container( + decoration: BoxDecoration( + border: Border.symmetric( + horizontal: BorderSide( + color: Theme.of(context).dividerColor, + width: 1, + )), + color: Theme.of(context).colorScheme.surface, + ), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Text(title, + textAlign: TextAlign.left, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 12, + fontWeight: FontWeight.bold, + )), + ), + ), + ); +} + +class _SearchItem extends StatelessWidget { + final String title; + final Uri? avatar; + final void Function() onPressed; + const _SearchItem({ + required this.title, + this.avatar, + required this.onPressed, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) => InkWell( + onTap: onPressed, + child: SizedBox( + width: 84, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Avatar( + mxContent: avatar, + name: title, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + title, + maxLines: 2, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 12, + ), + ), + ), + ], + ), + ), + ); +} diff --git a/lib/pages/chat_list/chat_list_drawer.dart b/lib/pages/chat_list/chat_list_drawer.dart new file mode 100644 index 00000000..95602b0f --- /dev/null +++ b/lib/pages/chat_list/chat_list_drawer.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:vrouter/vrouter.dart'; + +import 'package:fluffychat/pages/chat_list/chat_list.dart'; +import 'package:fluffychat/pages/chat_list/spaces_drawer.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class ChatListDrawer extends StatelessWidget { + final ChatListController controller; + const ChatListDrawer(this.controller, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => Drawer( + child: SafeArea( + child: Column( + children: [ + Expanded( + child: SpacesDrawer( + controller: controller, + ), + ), + const Divider(), + ListTile( + leading: Icon( + Icons.group_add_outlined, + color: Theme.of(context).colorScheme.onBackground, + ), + title: Text(L10n.of(context)!.createNewGroup), + onTap: () { + Scaffold.of(context).closeDrawer(); + VRouter.of(context).to('/newgroup'); + }, + ), + ListTile( + leading: Icon( + Icons.adaptive.share_outlined, + color: Theme.of(context).colorScheme.onBackground, + ), + title: Text(L10n.of(context)!.inviteContact), + onTap: () { + Scaffold.of(context).closeDrawer(); + FluffyShare.share( + L10n.of(context)!.inviteText( + Matrix.of(context).client.userID!, + 'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'), + context); + }, + ), + ListTile( + leading: Icon( + Icons.settings_outlined, + color: Theme.of(context).colorScheme.onBackground, + ), + title: Text(L10n.of(context)!.settings), + onTap: () { + Scaffold.of(context).closeDrawer(); + VRouter.of(context).to('/settings'); + }, + ), + ], + ), + ), + ); +} diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index 83e758aa..f1867a0e 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -1,15 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:animations/animations.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; import 'package:vrouter/vrouter.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/client_chooser_button.dart'; -import '../../widgets/matrix.dart'; +import 'package:fluffychat/widgets/matrix.dart'; class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { final ChatListController controller; @@ -21,23 +17,92 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { final selectMode = controller.selectMode; return AppBar( - elevation: controller.scrolledToTop ? 0 : null, - actionsIconTheme: IconThemeData( - color: controller.selectedRoomIds.isEmpty - ? null - : Theme.of(context).colorScheme.primary, - ), - leading: Matrix.of(context).isMultiAccount - ? ClientChooserButton(controller) - : selectMode == SelectMode.normal - ? null - : IconButton( - tooltip: L10n.of(context)!.cancel, - icon: const Icon(Icons.close_outlined), - onPressed: controller.cancelAction, - color: Theme.of(context).colorScheme.primary, + titleSpacing: 8, + automaticallyImplyLeading: false, + leading: selectMode == SelectMode.normal + ? null + : IconButton( + tooltip: L10n.of(context)!.cancel, + icon: const Icon(Icons.close_outlined), + onPressed: controller.cancelAction, + color: Theme.of(context).colorScheme.primary, + ), + title: selectMode == SelectMode.share + ? Text( + L10n.of(context)!.share, + key: const ValueKey(SelectMode.share), + ) + : selectMode == SelectMode.select + ? Text( + controller.selectedRoomIds.length.toString(), + key: const ValueKey(SelectMode.select), + ) + : TextField( + controller: controller.searchController, + textInputAction: TextInputAction.search, + onChanged: controller.onSearchEnter, + decoration: InputDecoration( + contentPadding: EdgeInsets.zero, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(90), + borderSide: BorderSide.none, + ), + hintText: L10n.of(context)!.search, + prefixIcon: controller.isSearchMode + ? IconButton( + tooltip: L10n.of(context)!.cancel, + icon: const Icon(Icons.close_outlined), + onPressed: controller.cancelSearch, + color: Theme.of(context).colorScheme.primary, + ) + : IconButton( + onPressed: Scaffold.of(context).openDrawer, + icon: Icon( + Icons.menu, + color: Theme.of(context).colorScheme.onBackground, + ), + ), + suffixIcon: Row( + mainAxisSize: MainAxisSize.min, + children: controller.isSearchMode + ? [ + if (controller.isSearching) + const CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + TextButton( + onPressed: controller.setServer, + style: TextButton.styleFrom( + textStyle: const TextStyle(fontSize: 12), + ), + child: Text( + controller.searchServer ?? + Matrix.of(context) + .client + .homeserver! + .host, + maxLines: 2, + ), + ), + ] + : [ + IconButton( + icon: Icon( + Icons.camera_alt_outlined, + color: Theme.of(context) + .colorScheme + .onBackground, + ), + tooltip: L10n.of(context)!.addToStory, + onPressed: () => + VRouter.of(context).to('/stories/create'), + ), + ClientChooserButton(controller), + const SizedBox(width: 12), + ], + ), + ), ), - centerTitle: false, actions: selectMode == SelectMode.share ? null : selectMode == SelectMode.select @@ -75,138 +140,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { onPressed: controller.archiveAction, ), ] - : [ - KeyBoardShortcuts( - keysToPress: { - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.keyF - }, - onKeysPressed: () => VRouter.of(context).to('/search'), - helpLabel: L10n.of(context)!.search, - child: IconButton( - icon: const Icon(Icons.search_outlined), - tooltip: L10n.of(context)!.search, - onPressed: () => VRouter.of(context).to('/search'), - ), - ), - if (selectMode == SelectMode.normal) - IconButton( - icon: const Icon(Icons.camera_alt_outlined), - tooltip: L10n.of(context)!.addToStory, - onPressed: () => - VRouter.of(context).to('/stories/create'), - ), - PopupMenuButton( - onSelected: controller.onPopupMenuSelect, - itemBuilder: (_) => [ - PopupMenuItem( - value: PopupMenuAction.setStatus, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.edit_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.setStatus), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.newGroup, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.group_add_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.createNewGroup), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.newSpace, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.group_work_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.createNewSpace), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.invite, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.adaptive.share_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.inviteContact), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.archive, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.archive_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.archive), - ], - ), - ), - PopupMenuItem( - value: PopupMenuAction.settings, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.settings_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.settings), - ], - ), - ), - ], - ), - ], - title: PageTransitionSwitcher( - reverse: false, - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.scaled, - fillColor: Colors.transparent, - child: child, - ); - }, - layoutBuilder: (children) => Stack( - alignment: AlignmentDirectional.centerStart, - children: children, - ), - child: selectMode == SelectMode.share - ? Text( - L10n.of(context)!.share, - key: const ValueKey(SelectMode.share), - ) - : selectMode == SelectMode.select - ? Text( - controller.selectedRoomIds.length.toString(), - key: const ValueKey(SelectMode.select), - ) - : (() { - final name = controller.activeSpaceId == null - ? AppConfig.applicationName - : Matrix.of(context) - .client - .getRoomById(controller.activeSpaceId!)! - .displayname; - return Text(name, key: ValueKey(name)); - })(), - ), + : null, ); } diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index 8c93fac4..d7881af1 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -4,12 +4,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:keyboard_shortcuts/keyboard_shortcuts.dart'; -import 'package:snapping_sheet/snapping_sheet.dart'; import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; -import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart'; -import 'package:fluffychat/widgets/connection_status_header.dart'; +import 'package:fluffychat/pages/chat_list/chat_list_drawer.dart'; import '../../widgets/matrix.dart'; import 'chat_list_body.dart'; import 'chat_list_header.dart'; @@ -25,8 +23,6 @@ class ChatListView extends StatelessWidget { stream: Matrix.of(context).onShareContentChanged.stream, builder: (_, __) { final selectMode = controller.selectMode; - final showSpaces = controller.spacesEntries.length > 1 && - controller.selectedRoomIds.isEmpty; return VWidgetGuard( onSystemPop: (redirector) async { final selMode = controller.selectMode; @@ -35,87 +31,28 @@ class ChatListView extends StatelessWidget { }, child: Scaffold( appBar: ChatListHeader(controller: controller), - body: LayoutBuilder( - builder: (context, size) { - controller.snappingSheetContainerSize = size; - return SnappingSheet( - key: ValueKey(Matrix.of(context).client.userID.toString() + - showSpaces.toString()), - controller: controller.snappingSheetController, - child: Column( - children: [ - AnimatedContainer( - height: controller.showChatBackupBanner ? 54 : 0, - duration: const Duration(milliseconds: 300), - clipBehavior: Clip.hardEdge, - curve: Curves.bounceInOut, - decoration: const BoxDecoration(), - child: Material( - color: Theme.of(context).colorScheme.surface, - child: ListTile( - leading: Image.asset( - 'assets/backup.png', - fit: BoxFit.contain, - width: 44, - ), - title: Text(L10n.of(context)!.setupChatBackupNow), - trailing: const Icon(Icons.chevron_right_outlined), - onTap: controller.firstRunBootstrapAction, - ), - ), - ), - Expanded(child: ChatListViewBody(controller)), - ], - ), - initialSnappingPosition: showSpaces - ? const SnappingPosition.pixels( - positionPixels: kSpacesBottomBarHeight) - : const SnappingPosition.factor(positionFactor: 0.0), - snappingPositions: showSpaces - ? const [ - SnappingPosition.pixels( - positionPixels: kSpacesBottomBarHeight), - SnappingPosition.factor(positionFactor: 0.5), - SnappingPosition.factor(positionFactor: 0.9), - ] - : [const SnappingPosition.factor(positionFactor: 0.0)], - sheetBelow: showSpaces - ? SnappingSheetContent( - childScrollController: - controller.snappingSheetScrollContentController, - draggable: true, - child: SpacesBottomBar(controller), - ) - : null, - ); - }, - ), + body: ChatListViewBody(controller), + drawer: ChatListDrawer(controller), floatingActionButton: selectMode == SelectMode.normal - ? Padding( - padding: showSpaces - ? const EdgeInsets.only(bottom: 64.0) - : const EdgeInsets.all(0), - child: KeyBoardShortcuts( - child: FloatingActionButton.extended( - isExtended: controller.scrolledToTop, - onPressed: () => - VRouter.of(context).to('/newprivatechat'), - icon: const Icon(CupertinoIcons.chat_bubble), - label: Text(L10n.of(context)!.newChat), - ), - keysToPress: { - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.keyN - }, - onKeysPressed: () => + ? KeyBoardShortcuts( + child: FloatingActionButton.extended( + isExtended: controller.scrolledToTop, + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.onPrimary, + onPressed: () => VRouter.of(context).to('/newprivatechat'), - helpLabel: L10n.of(context)!.newChat, + icon: const Icon(CupertinoIcons.chat_bubble), + label: Text(L10n.of(context)!.newChat), ), + keysToPress: { + LogicalKeyboardKey.controlLeft, + LogicalKeyboardKey.keyN + }, + onKeysPressed: () => + VRouter.of(context).to('/newprivatechat'), + helpLabel: L10n.of(context)!.newChat, ) : null, - bottomNavigationBar: const SafeArea( - child: ConnectionStatusHeader(), - ), ), ); }, diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 3c9a4b2f..6d463830 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -79,6 +79,16 @@ class ClientChooserButton extends StatelessWidget { ) .toList(), ], + PopupMenuItem( + value: AddAccountAction.addAccount, + child: Row( + children: [ + const Icon(Icons.person_add_outlined), + const SizedBox(width: 18), + Text(L10n.of(context)!.addAccount), + ], + ), + ), ]; } @@ -124,7 +134,8 @@ class ClientChooserButton extends StatelessWidget { ), PopupMenuButton( child: Material( - borderRadius: BorderRadius.zero, + color: Colors.transparent, + borderRadius: BorderRadius.circular(99), child: Avatar( mxContent: snapshot.data?.avatarUrl, name: snapshot.data?.displayName ?? @@ -158,6 +169,8 @@ class ClientChooserButton extends StatelessWidget { controller.setActiveClient(object); } else if (object is String) { controller.setActiveBundle(object); + } else if (object == AddAccountAction.addAccount) { + controller.addAccountAction(); } } @@ -222,3 +235,5 @@ class ClientChooserButton extends StatelessWidget { _handleKeyboardShortcut(matrix, lastIndex! - 1); } } + +enum AddAccountAction { addAccount } diff --git a/lib/pages/chat_list/spaces_bottom_bar.dart b/lib/pages/chat_list/spaces_bottom_bar.dart deleted file mode 100644 index 2b032652..00000000 --- a/lib/pages/chat_list/spaces_bottom_bar.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:salomon_bottom_bar/salomon_bottom_bar.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pages/chat_list/chat_list.dart'; -import 'package:fluffychat/pages/chat_list/spaces_drawer.dart'; -import 'package:fluffychat/pages/chat_list/spaces_entry.dart'; -import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -const kSpacesBottomBarHeight = 56.0; - -final GlobalKey _globalKey = GlobalKey(); - -class SpacesBottomBar extends StatelessWidget { - final ChatListController controller; - - const SpacesBottomBar(this.controller, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).navigationBarTheme.backgroundColor, - elevation: 6, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(AppConfig.borderRadius)), - clipBehavior: Clip.hardEdge, - child: SafeArea( - child: StreamBuilder( - stream: Matrix.of(context).client.onSync.stream.where((sync) => - (sync.rooms?.join?.values.any((r) => - r.state?.any((s) => s.type.startsWith('m.space')) ?? - false) ?? - false) || - (sync.rooms?.leave?.isNotEmpty ?? false)), - builder: (context, snapshot) { - return SingleChildScrollView( - controller: controller.snappingSheetScrollContentController, - child: AnimatedBuilder( - child: _SpacesBottomNavigation( - key: _globalKey, controller: controller), - builder: (context, child) { - if (controller.snappingSheetContainerSize == null) { - return child!; - } - final rawPosition = - controller.snappingSheetController.isAttached - ? controller.snappingSheetController.currentPosition - : 0; - final position = rawPosition / - controller.snappingSheetContainerSize!.maxHeight; - - if (rawPosition <= kSpacesBottomBarHeight) { - return child!; - } else if (position >= 0.5) { - return SpacesDrawer(controller: controller); - } else { - final normalized = (rawPosition - kSpacesBottomBarHeight) / - (controller.snappingSheetContainerSize!.maxHeight - - kSpacesBottomBarHeight) * - 2; - var boxHeight = (1 - normalized) * kSpacesBottomBarHeight; - if (boxHeight < 0) boxHeight = 0; - - return Column( - children: [ - SizedBox( - height: boxHeight, - child: ClipRect( - clipBehavior: Clip.hardEdge, - child: Opacity( - opacity: 1 - normalized, child: child!)), - ), - Opacity( - opacity: normalized, - child: SpacesDrawer(controller: controller), - ), - ], - ); - } - }, - animation: controller.snappingSheetController, - ), - ); - }, - ), - ), - ); - } -} - -class _SpacesBottomNavigation extends StatelessWidget { - final ChatListController controller; - - const _SpacesBottomNavigation({Key? key, required this.controller}) - : super(key: key); - - @override - Widget build(BuildContext context) { - final currentIndex = controller.spacesEntries.indexWhere((space) => - controller.activeSpacesEntry.runtimeType == space.runtimeType && - (controller.activeSpaceId == space.getSpace(context)?.id)) + - 1; - - return Container( - height: 56, - alignment: Alignment.center, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SalomonBottomBar( - itemPadding: const EdgeInsets.all(8), - currentIndex: currentIndex, - onTap: (i) => i == 0 - ? controller.expandSpaces() - : controller.setActiveSpacesEntry( - context, - controller.spacesEntries[i - 1], - ), - selectedItemColor: Theme.of(context).colorScheme.primary, - items: [ - SalomonBottomBarItem( - icon: const Icon(Icons.keyboard_arrow_up), - title: Text(L10n.of(context)!.showSpaces), - ), - ...controller.spacesEntries - .map((space) => _buildSpacesEntryUI(context, space)) - .toList(), - ], - ), - ), - ); - } - - SalomonBottomBarItem _buildSpacesEntryUI( - BuildContext context, SpacesEntry entry) { - final space = entry.getSpace(context); - if (space != null) { - return SalomonBottomBarItem( - icon: InkWell( - borderRadius: BorderRadius.circular(28), - onTap: () => controller.setActiveSpacesEntry( - context, - entry, - ), - onLongPress: () => controller.editSpace(context, space.id), - child: Avatar( - mxContent: space.avatar, - name: space.displayname, - size: 24, - fontSize: 12, - ), - ), - title: Text(entry.getName(context)), - ); - } - return SalomonBottomBarItem( - icon: entry.getIcon(false), - activeIcon: entry.getIcon(true), - title: Text(entry.getName(context)), - ); - } -} diff --git a/lib/pages/chat_list/spaces_drawer.dart b/lib/pages/chat_list/spaces_drawer.dart index 9feccf96..8ab385e5 100644 --- a/lib/pages/chat_list/spaces_drawer.dart +++ b/lib/pages/chat_list/spaces_drawer.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/pages/chat_list/spaces_entry.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -22,53 +23,93 @@ class SpacesDrawer extends StatelessWidget { // TODO(TheOeWithTheBraid): wait for space hierarchy https://gitlab.com/famedly/company/frontend/libraries/matrix_api_lite/-/merge_requests/58 - return WillPopScope( - onWillPop: () async { - controller.snapBackSpacesSheet(); - return false; - }, - child: Column( - children: List.generate(spaceHierarchy.length, (index) { - final space = spaceHierarchy.keys.toList()[index]; - final room = space.getSpace(context); - final active = currentIndex == index; + return ListView.builder( + itemCount: spaceHierarchy.length + 2, + itemBuilder: (context, i) { + if (i == spaceHierarchy.length) { return ListTile( - selected: active, - leading: index == 0 - ? const Icon(Icons.keyboard_arrow_down) - : room == null - ? space.getIcon(active) - : Avatar( - mxContent: room.avatar, - name: space.getName(context), - size: 24, - fontSize: 12, - ), - title: Text(space.getName(context)), - subtitle: room?.topic.isEmpty ?? true - ? null - : Tooltip( - message: room!.topic, - child: Text( - room.topic.replaceAll('\n', ' '), - softWrap: false, - overflow: TextOverflow.fade, - ), - ), - onTap: () => controller.setActiveSpacesEntry( - context, - space, + leading: CircleAvatar( + radius: Avatar.defaultSize / 2, + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + foregroundColor: + Theme.of(context).colorScheme.onSecondaryContainer, + child: const Icon( + Icons.archive_outlined, + ), ), - trailing: room != null - ? IconButton( - icon: const Icon(Icons.edit), + title: Text(L10n.of(context)!.archive), + onTap: () { + Scaffold.of(context).closeDrawer(); + VRouter.of(context).to('/archive'); + }, + ); + } + if (i == spaceHierarchy.length + 1) { + return ListTile( + leading: CircleAvatar( + child: const Icon(Icons.group_work_outlined), + radius: Avatar.defaultSize / 2, + backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + foregroundColor: + Theme.of(context).colorScheme.onSecondaryContainer, + ), + title: Text(L10n.of(context)!.createNewSpace), + onTap: () { + Scaffold.of(context).closeDrawer(); + VRouter.of(context).to('/newspace'); + }, + ); + } + final space = spaceHierarchy.keys.toList()[i]; + final room = space.getSpace(context); + final active = currentIndex == i; + return ListTile( + selected: active, + leading: room == null + ? CircleAvatar( + child: space.getIcon(active), + radius: Avatar.defaultSize / 2, + backgroundColor: + Theme.of(context).colorScheme.secondaryContainer, + foregroundColor: + Theme.of(context).colorScheme.onSecondaryContainer, + ) + : Avatar( + mxContent: room.avatar, + name: space.getName(context), + ), + title: Text( + space.getName(context), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + subtitle: room?.topic.isEmpty ?? true + ? null + : Tooltip( + message: room!.topic, + child: Text( + room.topic.replaceAll('\n', ' '), + softWrap: false, + overflow: TextOverflow.fade, + ), + ), + onTap: () => controller.setActiveSpacesEntry( + context, + space, + ), + trailing: room != null + ? SizedBox( + width: 32, + child: IconButton( + splashRadius: 24, + icon: const Icon(Icons.edit_outlined), tooltip: L10n.of(context)!.edit, onPressed: () => controller.editSpace(context, room.id), - ) - : null, - ); - }), - ), + ), + ) + : null, + ); + }, ); } } diff --git a/lib/pages/chat_list/stories_header.dart b/lib/pages/chat_list/stories_header.dart index 071d4245..3176625e 100644 --- a/lib/pages/chat_list/stories_header.dart +++ b/lib/pages/chat_list/stories_header.dart @@ -18,7 +18,8 @@ enum ContextualRoomAction { } class StoriesHeader extends StatelessWidget { - const StoriesHeader({Key? key}) : super(key: key); + final String filter; + const StoriesHeader({required this.filter, Key? key}) : super(key: key); void _addToStoryAction(BuildContext context) => VRouter.of(context).to('/stories/create'); @@ -105,7 +106,10 @@ class StoriesHeader extends StatelessWidget { onTap: () => _addToStoryAction(context), ); } - if (client.storiesRooms.isEmpty) { + if (client.storiesRooms.isEmpty || + !client.storiesRooms.any((room) => room.displayname + .toLowerCase() + .contains(filter.toLowerCase()))) { return Container(); } final ownStoryRoom = client.storiesRooms @@ -130,6 +134,11 @@ class StoriesHeader extends StatelessWidget { userId?.localpart ?? 'Unknown'; final avatarUrl = snapshot.data?.avatarUrl; + if (!displayname + .toLowerCase() + .contains(filter.toLowerCase())) { + return Container(); + } return _StoryButton( profile: Profile( displayName: displayname, @@ -139,7 +148,7 @@ class StoriesHeader extends StatelessWidget { hasPosts: room.hasPosts || room == ownStoryRoom, showEditFab: userId == client.userID, unread: room.membership == Membership.invite || - room.hasNewMessages, + (room.hasNewMessages && room.hasPosts), onPressed: () => _goToStoryAction(context, room.id), onLongPressed: () => _contextualActions(context, room), diff --git a/lib/pages/connect/connect_page_view.dart b/lib/pages/connect/connect_page_view.dart index 39ac584c..2695726e 100644 --- a/lib/pages/connect/connect_page_view.dart +++ b/lib/pages/connect/connect_page_view.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/connect/connect_page.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -90,14 +89,14 @@ class ConnectPageView extends StatelessWidget { child: TextField( controller: controller.usernameController, onSubmitted: (_) => controller.signUp(), - style: FluffyThemes.loginTextFieldStyle, - decoration: FluffyThemes.loginTextFieldDecoration( - prefixIcon: const Icon( - Icons.account_box_outlined, - color: Colors.black, - ), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.account_box_outlined), hintText: L10n.of(context)!.chooseAUsername, errorText: controller.signupError, + fillColor: Theme.of(context) + .colorScheme + .background + .withOpacity(0.75), ), ), ), @@ -106,12 +105,7 @@ class ConnectPageView extends StatelessWidget { child: Hero( tag: 'loginButton', child: ElevatedButton( - onPressed: controller.loading ? null : controller.signUp, - style: ElevatedButton.styleFrom( - primary: Colors.white.withAlpha(200), - onPrimary: Colors.black, - shadowColor: Colors.white, - ), + onPressed: controller.loading ? () {} : controller.signUp, child: controller.loading ? const LinearProgressIndicator() : Text(L10n.of(context)!.signUp), @@ -148,11 +142,6 @@ class ConnectPageView extends StatelessWidget { child: ElevatedButton( onPressed: () => controller .ssoLoginAction(identityProviders.single.id!), - style: ElevatedButton.styleFrom( - primary: Colors.white.withAlpha(200), - onPrimary: Colors.black, - shadowColor: Colors.white, - ), child: Text(identityProviders.single.name ?? identityProviders.single.brand ?? L10n.of(context)!.loginWithOneClick), @@ -176,11 +165,6 @@ class ConnectPageView extends StatelessWidget { tag: 'signinButton', child: ElevatedButton( onPressed: controller.loading ? () {} : controller.login, - style: ElevatedButton.styleFrom( - primary: Colors.white.withAlpha(200), - onPrimary: Colors.black, - shadowColor: Colors.white, - ), child: Text(L10n.of(context)!.login), ), ), diff --git a/lib/pages/connect/sso_button.dart b/lib/pages/connect/sso_button.dart index dc7589ce..1a099da2 100644 --- a/lib/pages/connect/sso_button.dart +++ b/lib/pages/connect/sso_button.dart @@ -22,17 +22,17 @@ class SsoButton extends StatelessWidget { onTap: onPressed, borderRadius: BorderRadius.circular(7), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Material( color: Colors.white, - borderRadius: BorderRadius.circular(7), + borderRadius: BorderRadius.circular(8), clipBehavior: Clip.hardEdge, child: Padding( - padding: const EdgeInsets.all(2.0), + padding: const EdgeInsets.all(4.0), child: identityProvider.icon == null ? const Icon(Icons.web_outlined) : CachedNetworkImage( diff --git a/lib/pages/device_settings/device_settings_view.dart b/lib/pages/device_settings/device_settings_view.dart index 9fe1ee47..5ac489fb 100644 --- a/lib/pages/device_settings/device_settings_view.dart +++ b/lib/pages/device_settings/device_settings_view.dart @@ -37,60 +37,53 @@ class DevicesSettingsView extends StatelessWidget { return const Center( child: CircularProgressIndicator.adaptive(strokeWidth: 2)); } - return Column( - children: [ - if (controller.thisDevice != null) - UserDeviceListItem( - controller.thisDevice!, - rename: controller.renameDeviceAction, - remove: (d) => controller.removeDevicesAction([d]), - verify: controller.verifyDeviceAction, - block: controller.blockDeviceAction, - unblock: controller.unblockDeviceAction, - ), - const Divider(height: 1), - if (controller.notThisDevice.isNotEmpty) - ListTile( - title: Text( - controller.errorDeletingDevices ?? - L10n.of(context)!.removeAllOtherDevices, - style: const TextStyle(color: Colors.red), - ), - trailing: controller.loadingDeletingDevices - ? const CircularProgressIndicator.adaptive( - strokeWidth: 2) - : const Icon(Icons.delete_outline), - onTap: controller.loadingDeletingDevices - ? null - : () => controller - .removeDevicesAction(controller.notThisDevice), - ), - const Divider(height: 1), - Expanded( - child: controller.notThisDevice.isEmpty - ? Center( - child: Icon( - Icons.devices_other, - size: 60, - color: Theme.of(context).secondaryHeaderColor, - ), - ) - : ListView.separated( - separatorBuilder: (BuildContext context, int i) => - const Divider(height: 1), - itemCount: controller.notThisDevice.length, - itemBuilder: (BuildContext context, int i) => - UserDeviceListItem( - controller.notThisDevice[i], - rename: controller.renameDeviceAction, - remove: (d) => controller.removeDevicesAction([d]), - verify: controller.verifyDeviceAction, - block: controller.blockDeviceAction, - unblock: controller.unblockDeviceAction, - ), + return ListView.builder( + itemCount: controller.notThisDevice.length + 1, + itemBuilder: (BuildContext context, int i) { + if (i == 0) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (controller.thisDevice != null) + UserDeviceListItem( + controller.thisDevice!, + rename: controller.renameDeviceAction, + remove: (d) => controller.removeDevicesAction([d]), + verify: controller.verifyDeviceAction, + block: controller.blockDeviceAction, + unblock: controller.unblockDeviceAction, ), - ), - ], + const Divider(height: 1), + if (controller.notThisDevice.isNotEmpty) + ListTile( + title: Text( + controller.errorDeletingDevices ?? + L10n.of(context)!.removeAllOtherDevices, + style: const TextStyle(color: Colors.red), + ), + trailing: controller.loadingDeletingDevices + ? const CircularProgressIndicator.adaptive( + strokeWidth: 2) + : const Icon(Icons.delete_outline), + onTap: controller.loadingDeletingDevices + ? null + : () => controller.removeDevicesAction( + controller.notThisDevice), + ), + const Divider(height: 1), + ], + ); + } + i--; + return UserDeviceListItem( + controller.notThisDevice[i], + rename: controller.renameDeviceAction, + remove: (d) => controller.removeDevicesAction([d]), + verify: controller.verifyDeviceAction, + block: controller.blockDeviceAction, + unblock: controller.unblockDeviceAction, + ); + }, ); }, ), diff --git a/lib/pages/device_settings/user_device_list_item.dart b/lib/pages/device_settings/user_device_list_item.dart index a741c01c..e65dc6db 100644 --- a/lib/pages/device_settings/user_device_list_item.dart +++ b/lib/pages/device_settings/user_device_list_item.dart @@ -108,12 +108,13 @@ class UserDeviceListItem extends StatelessWidget { ), title: Row( children: [ - Text( - userDevice.displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, + Expanded( + child: Text( + userDevice.displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), - const Spacer(), if (keys != null) Text( keys.blocked diff --git a/lib/pages/homeserver_picker/homeserver_picker_view.dart b/lib/pages/homeserver_picker/homeserver_picker_view.dart index 76f7e301..23938723 100644 --- a/lib/pages/homeserver_picker/homeserver_picker_view.dart +++ b/lib/pages/homeserver_picker/homeserver_picker_view.dart @@ -5,7 +5,6 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:vrouter/vrouter.dart'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'homeserver_picker.dart'; @@ -22,131 +21,127 @@ class HomeserverPickerView extends StatelessWidget { appBar: VRouter.of(context).path == '/home' ? null : AppBar(title: Text(L10n.of(context)!.addAccount)), - body: Column( - children: [ - Expanded( - child: ListView( - children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 300), - constraints: BoxConstraints( - maxHeight: controller.displayServerList ? 0 : 256), - alignment: Alignment.center, - child: Image.asset('assets/info-logo.png'), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - focusNode: controller.homeserverFocusNode, - controller: controller.homeserverController, - onChanged: controller.onChanged, - style: FluffyThemes.loginTextFieldStyle, - decoration: FluffyThemes.loginTextFieldDecoration( - labelText: L10n.of(context)!.homeserver, - hintText: L10n.of(context)!.enterYourHomeserver, - suffixIcon: const Icon( - Icons.search, - color: Colors.black, - ), - errorText: controller.error, - ), - readOnly: !AppConfig.allowOtherHomeservers, - onSubmitted: (_) => controller.checkHomeserverAction(), - autocorrect: false, + body: SafeArea( + child: Column( + children: [ + Expanded( + child: ListView( + children: [ + Container( + alignment: Alignment.center, + height: 256, + child: Image.asset('assets/info-logo.png'), ), - ), - if (controller.displayServerList) Padding( padding: const EdgeInsets.all(16.0), - child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - color: Colors.white.withAlpha(200), - clipBehavior: Clip.hardEdge, - child: benchmarkResults == null - ? const Center( - child: Padding( - padding: EdgeInsets.all(16.0), - child: CircularProgressIndicator.adaptive(), - )) - : Column( - children: controller.filteredHomeservers - .map( - (server) => ListTile( - trailing: IconButton( - icon: const Icon( - Icons.info_outlined, - color: Colors.black, - ), - onPressed: () => - controller.showServerInfo(server), - ), - onTap: () => controller.setServer( - server.homeserver.baseUrl.host), - title: Text( - server.homeserver.baseUrl.host, - style: const TextStyle( - color: Colors.black), - ), - subtitle: Text( - server.homeserver.description ?? '', - style: TextStyle( - color: Colors.grey.shade700), - ), - ), - ) - .toList(), - ), + child: TextField( + focusNode: controller.homeserverFocusNode, + controller: controller.homeserverController, + onChanged: controller.onChanged, + decoration: InputDecoration( + prefixText: '${L10n.of(context)!.homeserver}: ', + hintText: L10n.of(context)!.enterYourHomeserver, + suffixIcon: const Icon(Icons.search), + errorText: controller.error, + fillColor: Theme.of(context) + .colorScheme + .background + .withOpacity(0.75), + ), + readOnly: !AppConfig.allowOtherHomeservers, + onSubmitted: (_) => controller.checkHomeserverAction(), + autocorrect: false, ), ), - Wrap( - alignment: WrapAlignment.center, - children: [ - TextButton( - onPressed: () => launch(AppConfig.privacyUrl), - child: Text( - L10n.of(context)!.privacy, - style: const TextStyle( - decoration: TextDecoration.underline, - color: Colors.white, - ), + if (controller.displayServerList) + Padding( + padding: const EdgeInsets.all(16.0), + child: Material( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + color: Colors.white.withAlpha(200), + clipBehavior: Clip.hardEdge, + child: benchmarkResults == null + ? const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator.adaptive(), + )) + : Column( + children: controller.filteredHomeservers + .map( + (server) => ListTile( + trailing: IconButton( + icon: const Icon( + Icons.info_outlined, + color: Colors.black, + ), + onPressed: () => + controller.showServerInfo(server), + ), + onTap: () => controller.setServer( + server.homeserver.baseUrl.host), + title: Text( + server.homeserver.baseUrl.host, + style: const TextStyle( + color: Colors.black), + ), + subtitle: Text( + server.homeserver.description ?? '', + style: TextStyle( + color: Colors.grey.shade700), + ), + ), + ) + .toList(), + ), ), ), - TextButton( - onPressed: () => PlatformInfos.showDialog(context), - child: Text( - L10n.of(context)!.about, - style: const TextStyle( - decoration: TextDecoration.underline, - color: Colors.white, + Wrap( + alignment: WrapAlignment.center, + children: [ + TextButton( + onPressed: () => launch(AppConfig.privacyUrl), + child: Text( + L10n.of(context)!.privacy, + style: const TextStyle( + decoration: TextDecoration.underline, + color: Colors.white, + ), ), ), - ), - ], - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: Hero( - tag: 'loginButton', - child: ElevatedButton( - onPressed: controller.isLoading - ? () {} - : controller.checkHomeserverAction, - style: ElevatedButton.styleFrom( - primary: Colors.white.withAlpha(200), - onPrimary: Colors.black, - shadowColor: Colors.white, - ), - child: controller.isLoading - ? const LinearProgressIndicator() - : Text(L10n.of(context)!.connect), + TextButton( + onPressed: () => PlatformInfos.showDialog(context), + child: Text( + L10n.of(context)!.about, + style: const TextStyle( + decoration: TextDecoration.underline, + color: Colors.white, + ), + ), + ), + ], + ), + ], ), ), - ), - ], + Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + child: Hero( + tag: 'loginButton', + child: ElevatedButton( + onPressed: controller.isLoading + ? null + : controller.checkHomeserverAction, + child: controller.isLoading + ? const LinearProgressIndicator() + : Text(L10n.of(context)!.connect), + ), + ), + ), + ], + ), ), ); } diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index 7d1c69d1..b74ac9f8 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'login.dart'; @@ -44,16 +43,16 @@ class LoginView extends StatelessWidget { controller: controller.usernameController, textInputAction: TextInputAction.next, keyboardType: TextInputType.emailAddress, - style: FluffyThemes.loginTextFieldStyle, autofillHints: controller.loading ? null : [AutofillHints.username], - decoration: FluffyThemes.loginTextFieldDecoration( - prefixIcon: const Icon( - Icons.account_box_outlined, - color: Colors.black, - ), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.account_box_outlined), errorText: controller.usernameError, hintText: L10n.of(context)!.emailOrUsername, + fillColor: Theme.of(context) + .colorScheme + .background + .withOpacity(0.75), ), ), ), @@ -68,12 +67,8 @@ class LoginView extends StatelessWidget { textInputAction: TextInputAction.next, obscureText: !controller.showPassword, onSubmitted: controller.login, - style: FluffyThemes.loginTextFieldStyle, - decoration: FluffyThemes.loginTextFieldDecoration( - prefixIcon: const Icon( - Icons.lock_outlined, - color: Colors.black, - ), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.lock_outlined), errorText: controller.passwordError, suffixIcon: IconButton( tooltip: L10n.of(context)!.showPassword, @@ -86,6 +81,10 @@ class LoginView extends StatelessWidget { onPressed: controller.toggleShowPassword, ), hintText: L10n.of(context)!.password, + fillColor: Theme.of(context) + .colorScheme + .background + .withOpacity(0.75), ), ), ), @@ -97,11 +96,6 @@ class LoginView extends StatelessWidget { onPressed: controller.loading ? null : () => controller.login(context), - style: ElevatedButton.styleFrom( - primary: Colors.white.withAlpha(200), - onPrimary: Colors.black, - shadowColor: Colors.white, - ), child: controller.loading ? const LinearProgressIndicator() : Text(L10n.of(context)!.login), @@ -126,11 +120,7 @@ class LoginView extends StatelessWidget { child: ElevatedButton( onPressed: controller.loading ? () {} : controller.passwordForgotten, - style: ElevatedButton.styleFrom( - primary: Colors.white.withAlpha(156), - onPrimary: Colors.red, - shadowColor: Colors.white, - ), + style: ElevatedButton.styleFrom(onPrimary: Colors.red), child: Text(L10n.of(context)!.passwordForgotten), ), ), diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart deleted file mode 100644 index 12816558..00000000 --- a/lib/pages/search/search.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; -import 'package:vrouter/vrouter.dart'; - -import 'package:fluffychat/utils/famedlysdk_store.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:fluffychat/widgets/public_room_bottom_sheet.dart'; -import 'search_view.dart'; - -class Search extends StatefulWidget { - const Search({Key? key}) : super(key: key); - - @override - SearchController createState() => SearchController(); -} - -class SearchController extends State { - final TextEditingController controller = TextEditingController(); - Future? publicRoomsResponse; - String? lastServer; - Timer? _coolDown; - String? genericSearchTerm; - - void search(String query) async { - setState(() {}); - _coolDown?.cancel(); - _coolDown = Timer( - const Duration(milliseconds: 500), - () => setState(() { - genericSearchTerm = query; - publicRoomsResponse = null; - searchUser(context, controller.text); - }), - ); - } - - void joinGroupAction(PublicRoomsChunk room) { - showModalBottomSheet( - context: context, - builder: (c) => PublicRoomBottomSheet( - roomAlias: room.canonicalAlias ?? room.roomId, - outerContext: context, - chunk: room, - ), - ); - } - - String? server; - - static const String _serverStoreNamespace = 'im.fluffychat.search.server'; - - void setServer() async { - final newServer = await showTextInputDialog( - useRootNavigator: false, - title: L10n.of(context)!.changeTheHomeserver, - context: context, - okLabel: L10n.of(context)!.ok, - cancelLabel: L10n.of(context)!.cancel, - textFields: [ - DialogTextField( - prefixText: 'https://', - hintText: Matrix.of(context).client.homeserver?.host, - initialText: server, - keyboardType: TextInputType.url, - autocorrect: false) - ]); - if (newServer == null) return; - Store().setItem(_serverStoreNamespace, newServer.single); - setState(() { - server = newServer.single; - }); - } - - String? currentSearchTerm; - List foundProfiles = []; - - static const searchUserDirectoryLimit = 10; - - void searchUser(BuildContext context, String text) async { - if (text.isEmpty) { - setState(() { - foundProfiles = []; - }); - } - currentSearchTerm = text; - if (currentSearchTerm?.isEmpty ?? true) return; - final matrix = Matrix.of(context); - SearchUserDirectoryResponse? response; - try { - response = await matrix.client.searchUserDirectory( - text, - limit: searchUserDirectoryLimit, - ); - } catch (_) {} - foundProfiles = List.from(response?.results ?? []); - if (foundProfiles.isEmpty && text.isValidMatrixId && text.sigil == '@') { - foundProfiles.add(Profile.fromJson({ - 'displayname': text.localpart, - 'user_id': text, - })); - } - setState(() {}); - } - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) async { - controller.text = VRouter.of(context).queryParameters['query'] ?? ''; - final server = await Store().getItem(_serverStoreNamespace); - if (server?.isNotEmpty ?? false) { - this.server = server; - } - search(controller.text); - }); - } - - @override - Widget build(BuildContext context) => SearchView(this); -} diff --git a/lib/pages/search/search_view.dart b/lib/pages/search/search_view.dart deleted file mode 100644 index 0bf6f95a..00000000 --- a/lib/pages/search/search_view.dart +++ /dev/null @@ -1,299 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:matrix/matrix.dart'; -import 'package:vrouter/vrouter.dart'; - -import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; -import 'package:fluffychat/utils/string_extension.dart'; -import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/contacts_list.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import '../../utils/localized_exception_extension.dart'; -import '../../utils/platform_infos.dart'; -import 'search.dart'; - -class SearchView extends StatelessWidget { - final SearchController controller; - - const SearchView(this.controller, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final server = controller.genericSearchTerm?.isValidMatrixId ?? false - ? controller.genericSearchTerm!.domain - : controller.server; - if (controller.lastServer != server) { - controller.lastServer = server; - controller.publicRoomsResponse = null; - } - controller.publicRoomsResponse ??= Matrix.of(context) - .client - .queryPublicRooms( - server: server, - filter: PublicRoomQueryFilter( - genericSearchTerm: controller.genericSearchTerm, - ), - ) - .catchError((error) { - if (!(controller.genericSearchTerm?.isValidMatrixId ?? false)) { - throw error; - } - return QueryPublicRoomsResponse.fromJson({ - 'chunk': [], - }); - }).then((QueryPublicRoomsResponse res) { - final genericSearchTerm = controller.genericSearchTerm; - if (genericSearchTerm != null && - !res.chunk.any( - (room) => room.canonicalAlias == controller.genericSearchTerm)) { - // we have to tack on the original alias - res.chunk.add( - PublicRoomsChunk( - name: genericSearchTerm, - numJoinedMembers: 0, - roomId: '!unknown', - worldReadable: true, - guestCanJoin: true, - ), - ); - } - return res; - }); - - final rooms = List.from(Matrix.of(context).client.rooms); - rooms.removeWhere( - (room) => - room.lastEvent == null || - !room.displayname.toLowerCase().removeDiacritics().contains( - controller.controller.text.toLowerCase().removeDiacritics()), - ); - const tabCount = 3; - return DefaultTabController( - length: tabCount, - initialIndex: controller.controller.text.startsWith('#') ? 0 : 1, - child: Scaffold( - appBar: AppBar( - leading: const BackButton(), - titleSpacing: 0, - title: TextField( - autofocus: true, - controller: controller.controller, - decoration: InputDecoration( - suffix: const Icon(Icons.search_outlined), - hintText: L10n.of(context)!.search, - contentPadding: const EdgeInsets.symmetric(horizontal: 16), - ), - onChanged: controller.search, - ), - bottom: TabBar( - indicatorColor: Theme.of(context).colorScheme.secondary, - labelColor: Theme.of(context).colorScheme.secondary, - unselectedLabelColor: Theme.of(context).textTheme.bodyText1!.color, - labelStyle: const TextStyle(fontSize: 16), - labelPadding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 0, - ), - tabs: [ - Tab(child: Text(L10n.of(context)!.discover, maxLines: 1)), - Tab(child: Text(L10n.of(context)!.chats, maxLines: 1)), - Tab(child: Text(L10n.of(context)!.people, maxLines: 1)), - ], - ), - ), - body: TabBarView( - children: [ - ListView( - keyboardDismissBehavior: PlatformInfos.isIOS - ? ScrollViewKeyboardDismissBehavior.onDrag - : ScrollViewKeyboardDismissBehavior.manual, - children: [ - const SizedBox(height: 12), - ListTile( - leading: CircleAvatar( - foregroundColor: Theme.of(context).colorScheme.secondary, - backgroundColor: Theme.of(context).secondaryHeaderColor, - child: const Icon(Icons.edit_outlined), - ), - title: Text(L10n.of(context)!.changeTheServer), - onTap: controller.setServer, - ), - FutureBuilder( - future: controller.publicRoomsResponse, - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 32), - const Icon( - Icons.error_outlined, - size: 80, - color: Colors.grey, - ), - Center( - child: Text( - snapshot.error!.toLocalizedString(context), - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.grey, - fontSize: 16, - ), - ), - ), - ], - ); - } - if (snapshot.connectionState != ConnectionState.done) { - return const Center( - child: CircularProgressIndicator.adaptive( - strokeWidth: 2)); - } - final publicRoomsResponse = snapshot.data!; - if (publicRoomsResponse.chunk.isEmpty) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 32), - const Icon( - Icons.search_outlined, - size: 80, - color: Colors.grey, - ), - Center( - child: Text( - L10n.of(context)!.noPublicRoomsFound, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.grey, - fontSize: 16, - ), - ), - ), - ], - ); - } - return GridView.builder( - shrinkWrap: true, - padding: const EdgeInsets.all(12), - physics: const NeverScrollableScrollPhysics(), - gridDelegate: - const 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: () => controller.joinGroupAction( - publicRoomsResponse.chunk[i], - ), - borderRadius: BorderRadius.circular(16), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Avatar( - mxContent: - publicRoomsResponse.chunk[i].avatarUrl, - name: publicRoomsResponse.chunk[i].name, - ), - Text( - publicRoomsResponse.chunk[i].name!, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - textAlign: TextAlign.center, - ), - Text( - L10n.of(context)!.countParticipants( - publicRoomsResponse - .chunk[i].numJoinedMembers), - style: const TextStyle(fontSize: 10.5), - maxLines: 1, - textAlign: TextAlign.center, - ), - Text( - publicRoomsResponse.chunk[i].topic ?? - L10n.of(context)!.noDescription, - maxLines: 4, - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ), - ); - }), - ], - ), - ListView.builder( - keyboardDismissBehavior: PlatformInfos.isIOS - ? ScrollViewKeyboardDismissBehavior.onDrag - : ScrollViewKeyboardDismissBehavior.manual, - itemCount: rooms.length, - itemBuilder: (_, i) => ChatListItem(rooms[i]), - ), - controller.foundProfiles.isNotEmpty - ? ListView.builder( - keyboardDismissBehavior: PlatformInfos.isIOS - ? ScrollViewKeyboardDismissBehavior.onDrag - : ScrollViewKeyboardDismissBehavior.manual, - itemCount: controller.foundProfiles.length, - itemBuilder: (BuildContext context, int i) { - final foundProfile = controller.foundProfiles[i]; - return ListTile( - onTap: () async { - final roomID = await showFutureLoadingDialog( - context: context, - future: () async { - final client = Matrix.of(context).client; - final roomId = await client - .startDirectChat(foundProfile.userId); - return roomId; - }, - ); - if (roomID.error == null) { - VRouter.of(context) - .toSegments(['rooms', roomID.result!]); - } - }, - leading: Avatar( - mxContent: foundProfile.avatarUrl, - name: foundProfile.displayName ?? foundProfile.userId, - //size: 24, - ), - title: Text( - foundProfile.displayName ?? - foundProfile.userId.localpart!, - style: const TextStyle(), - maxLines: 1, - ), - subtitle: Text( - foundProfile.userId, - maxLines: 1, - style: const TextStyle( - fontSize: 12, - ), - ), - ); - }, - ) - : ContactsList(searchController: controller.controller), - ], - ), - ), - ); - } -} diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index c2146f4d..0283ab53 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -37,7 +37,7 @@ class SettingsView extends StatelessWidget { ), ], body: ListTileTheme( - iconColor: Theme.of(context).textTheme.bodyText1!.color, + iconColor: Theme.of(context).colorScheme.onBackground, child: ListView( children: [ ListTile( diff --git a/lib/pages/sign_up/signup_view.dart b/lib/pages/sign_up/signup_view.dart index d878c6a5..754c2358 100644 --- a/lib/pages/sign_up/signup_view.dart +++ b/lib/pages/sign_up/signup_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/widgets/layouts/login_scaffold.dart'; import 'signup.dart'; @@ -38,11 +37,8 @@ class SignupPageView extends StatelessWidget { controller: controller.passwordController, obscureText: !controller.showPassword, validator: controller.password1TextFieldValidator, - decoration: FluffyThemes.loginTextFieldDecoration( - prefixIcon: const Icon( - Icons.vpn_key_outlined, - color: Colors.black, - ), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.vpn_key_outlined), suffixIcon: IconButton( tooltip: L10n.of(context)!.showPassword, icon: Icon( @@ -53,7 +49,12 @@ class SignupPageView extends StatelessWidget { ), onPressed: controller.toggleShowPassword, ), + errorStyle: const TextStyle(color: Colors.orange), hintText: L10n.of(context)!.chooseAStrongPassword, + fillColor: Theme.of(context) + .colorScheme + .background + .withOpacity(0.75), ), ), ), @@ -68,12 +69,14 @@ class SignupPageView extends StatelessWidget { controller: controller.password2Controller, obscureText: !controller.showPassword, validator: controller.password2TextFieldValidator, - decoration: FluffyThemes.loginTextFieldDecoration( - prefixIcon: const Icon( - Icons.repeat_outlined, - color: Colors.black, - ), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.repeat_outlined), hintText: L10n.of(context)!.repeatPassword, + errorStyle: const TextStyle(color: Colors.orange), + fillColor: Theme.of(context) + .colorScheme + .background + .withOpacity(0.75), ), ), ), @@ -87,16 +90,19 @@ class SignupPageView extends StatelessWidget { autofillHints: controller.loading ? null : [AutofillHints.username], validator: controller.emailTextFieldValidator, - decoration: FluffyThemes.loginTextFieldDecoration( - prefixIcon: const Icon( - Icons.mail_outlined, - color: Colors.black, - ), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.mail_outlined), hintText: L10n.of(context)!.enterAnEmailAddress, errorText: controller.error, - errorColor: controller.emailController.text.isEmpty - ? Colors.orangeAccent - : null, + fillColor: Theme.of(context) + .colorScheme + .background + .withOpacity(0.75), + errorStyle: TextStyle( + color: controller.emailController.text.isEmpty + ? Colors.orangeAccent + : Colors.orange, + ), ), ), ), @@ -106,11 +112,6 @@ class SignupPageView extends StatelessWidget { padding: const EdgeInsets.all(16), child: ElevatedButton( onPressed: controller.loading ? () {} : controller.signup, - style: ElevatedButton.styleFrom( - primary: Colors.white.withAlpha(200), - onPrimary: Colors.black, - shadowColor: Colors.white, - ), child: controller.loading ? const LinearProgressIndicator() : Text(L10n.of(context)!.signUp), diff --git a/macos/Podfile.lock b/macos/Podfile.lock index d92fac94..6b664a2b 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -12,10 +12,6 @@ PODS: - FlutterMacOS - emoji_picker_flutter (0.0.1): - FlutterMacOS - - file_selector_macos (0.0.1): - - FlutterMacOS - - flutter_app_badger (1.3.0): - - FlutterMacOS - flutter_local_notifications (0.0.1): - FlutterMacOS - flutter_secure_storage_macos (3.3.1): @@ -24,7 +20,7 @@ PODS: - FlutterMacOS - flutter_webrtc (0.7.1): - FlutterMacOS - - WebRTC-SDK (= 97.4692.02) + - WebRTC-SDK (= 97.4692.05) - FlutterMacOS (1.0.0) - FMDB (2.7.5): - FMDB/standard (= 2.7.5) @@ -33,13 +29,15 @@ PODS: - FlutterMacOS - just_audio (0.0.1): - FlutterMacOS - - package_info (0.0.1): - - FlutterMacOS - package_info_plus_macos (0.0.1): - FlutterMacOS - path_provider_macos (0.0.1): - FlutterMacOS - ReachabilitySwift (5.0.0) + - record_macos (1.0.0): + - FlutterMacOS + - share_plus_macos (0.0.1): + - FlutterMacOS - shared_preferences_macos (0.0.1): - FlutterMacOS - sqflite (0.0.2): @@ -51,7 +49,7 @@ PODS: - FlutterMacOS - wakelock_macos (0.0.1): - FlutterMacOS - - WebRTC-SDK (97.4692.02) + - WebRTC-SDK (97.4692.05) DEPENDENCIES: - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) @@ -60,8 +58,6 @@ DEPENDENCIES: - desktop_lifecycle (from `Flutter/ephemeral/.symlinks/plugins/desktop_lifecycle/macos`) - device_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus_macos/macos`) - emoji_picker_flutter (from `Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos`) - - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - - flutter_app_badger (from `Flutter/ephemeral/.symlinks/plugins/flutter_app_badger/macos`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - flutter_web_auth (from `Flutter/ephemeral/.symlinks/plugins/flutter_web_auth/macos`) @@ -69,9 +65,10 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos`) - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`) - - package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`) - package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) + - record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`) + - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`) - shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) @@ -97,10 +94,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus_macos/macos emoji_picker_flutter: :path: Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos - file_selector_macos: - :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos - flutter_app_badger: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_app_badger/macos flutter_local_notifications: :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos flutter_secure_storage_macos: @@ -115,12 +108,14 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos just_audio: :path: Flutter/ephemeral/.symlinks/plugins/just_audio/macos - package_info: - :path: Flutter/ephemeral/.symlinks/plugins/package_info/macos package_info_plus_macos: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos path_provider_macos: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos + record_macos: + :path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos + share_plus_macos: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos shared_preferences_macos: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos sqflite: @@ -139,26 +134,25 @@ SPEC CHECKSUMS: desktop_lifecycle: a600c10e12fe033c7be9078f2e929b8241f2c1e3 device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7 emoji_picker_flutter: 533634326b1c5de9a181ba14b9758e6dfe967a20 - file_selector_macos: ff6dc948d4ddd34e8602a1f60b7d0b4cc6051a47 - flutter_app_badger: 55a64b179f8438e89d574320c77b306e327a1730 flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa flutter_web_auth: ae2c29ca9b98c00b4e0e8c0919bb4a05d44b76df - flutter_webrtc: 238124d0a7ba1c43543791f31a92a672370497c2 + flutter_webrtc: 37c4efd66d9d306878c1323d5ac5a1d10c748b3a FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a geolocator_apple: 821be05bbdb1b49500e029ebcbf2d6acf2dfb966 just_audio: 9b67ca7b97c61cfc9784ea23cd8cc55eb226d489 - package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + record_macos: dcf4f2bb654970437e012521cb4ea1fca4f78bb9 + share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4 shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9 - WebRTC-SDK: dda4e50186f9eed672dc6bcf4faafb30c6ce48e3 + WebRTC-SDK: a6ee40bda0e3f7dba057907c3897374005c5715b PODFILE CHECKSUM: 9b8d08a513b178c33212d1b54cc9e3cba756d95b diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 1425615f..0f19f640 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -268,17 +268,16 @@ "${BUILT_PRODUCTS_DIR}/desktop_lifecycle/desktop_lifecycle.framework", "${BUILT_PRODUCTS_DIR}/device_info_plus_macos/device_info_plus_macos.framework", "${BUILT_PRODUCTS_DIR}/emoji_picker_flutter/emoji_picker_flutter.framework", - "${BUILT_PRODUCTS_DIR}/file_selector_macos/file_selector_macos.framework", - "${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework", "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", "${BUILT_PRODUCTS_DIR}/flutter_secure_storage_macos/flutter_secure_storage_macos.framework", "${BUILT_PRODUCTS_DIR}/flutter_web_auth/flutter_web_auth.framework", "${BUILT_PRODUCTS_DIR}/flutter_webrtc/flutter_webrtc.framework", "${BUILT_PRODUCTS_DIR}/geolocator_apple/geolocator_apple.framework", "${BUILT_PRODUCTS_DIR}/just_audio/just_audio.framework", - "${BUILT_PRODUCTS_DIR}/package_info/package_info.framework", "${BUILT_PRODUCTS_DIR}/package_info_plus_macos/package_info_plus_macos.framework", "${BUILT_PRODUCTS_DIR}/path_provider_macos/path_provider_macos.framework", + "${BUILT_PRODUCTS_DIR}/record_macos/record_macos.framework", + "${BUILT_PRODUCTS_DIR}/share_plus_macos/share_plus_macos.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_macos/shared_preferences_macos.framework", "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_macos/url_launcher_macos.framework", @@ -296,17 +295,16 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/desktop_lifecycle.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/emoji_picker_flutter.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_selector_macos.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_web_auth.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_webrtc.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/geolocator_apple.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/just_audio.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_macos.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/record_macos.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_macos.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_macos.framework", diff --git a/pubspec.lock b/pubspec.lock index 450a812e..212d7888 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1423,13 +1423,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.27.3" - salomon_bottom_bar: - dependency: "direct main" - description: - name: salomon_bottom_bar - url: "https://pub.dartlang.org" - source: hosted - version: "3.3.0" scroll_to_index: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 54cbfdd7..281df392 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,7 +71,6 @@ dependencies: qr_flutter: ^4.0.0 receive_sharing_intent: ^1.4.5 record: ^4.1.1 - salomon_bottom_bar: ^3.2.0 scroll_to_index: ^3.0.1 sentry: ^6.3.0 share_plus: ^4.0.9