Merge branch 'braid/multi-account-fixes' into 'main'

fix: multi-account related issues

See merge request famedly/fluffychat!874
This commit is contained in:
Krille Fear 2022-06-02 07:54:36 +00:00
commit 8c86ff4331
9 changed files with 289 additions and 195 deletions

View File

@ -82,7 +82,10 @@ class ChatListController extends State<ChatList> with TickerProviderStateMixin {
} }
void setActiveSpacesEntry(BuildContext context, SpacesEntry? spaceId) { void setActiveSpacesEntry(BuildContext context, SpacesEntry? spaceId) {
if (snappingSheetController.currentPosition != kSpacesBottomBarHeight) { if ((snappingSheetController.isAttached
? snappingSheetController.currentPosition
: 0) !=
kSpacesBottomBarHeight) {
snapBackSpacesSheet(); snapBackSpacesSheet();
} }
setState(() => _activeSpacesEntry = spaceId); setState(() => _activeSpacesEntry = spaceId);

View File

@ -9,6 +9,7 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_list/chat_list.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/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.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/pages/chat_list/stories_header.dart';
import '../../utils/stream_extension.dart'; import '../../utils/stream_extension.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
@ -28,7 +29,7 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
// used to check the animation direction // used to check the animation direction
String? _lastUserId; String? _lastUserId;
String? _lastSpaceId; SpacesEntry? _lastSpace;
@override @override
void initState() { void initState() {
@ -175,8 +176,10 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
return SharedAxisTransition( return SharedAxisTransition(
animation: primaryAnimation, animation: primaryAnimation,
secondaryAnimation: secondaryAnimation, secondaryAnimation: secondaryAnimation,
transitionType: transitionType: (widget.controller.snappingSheetController.isAttached
widget.controller.snappingSheetController.currentPosition == ? widget
.controller.snappingSheetController.currentPosition
: 0) ==
kSpacesBottomBarHeight kSpacesBottomBarHeight
? SharedAxisTransitionType.horizontal ? SharedAxisTransitionType.horizontal
: SharedAxisTransitionType.vertical, : SharedAxisTransitionType.vertical,
@ -208,13 +211,13 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
} }
// otherwise, the space changed... // otherwise, the space changed...
else { else {
reversed = widget.controller.spaces reversed = widget.controller.spacesEntries
.indexWhere((element) => element.id == _lastSpaceId) < .indexWhere((element) => element == _lastSpace) <
widget.controller.spaces.indexWhere( widget.controller.spacesEntries.indexWhere(
(element) => element.id == widget.controller.activeSpaceId); (element) => element == widget.controller.activeSpacesEntry);
} }
_lastUserId = newClient.userID; _lastUserId = newClient.userID;
_lastSpaceId = widget.controller.activeSpaceId; _lastSpace = widget.controller.activeSpacesEntry;
return reversed; return reversed;
} }
} }

View File

@ -0,0 +1,215 @@
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';
class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
final ChatListController controller;
const ChatListHeader({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
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,
),
centerTitle: false,
actions: selectMode == SelectMode.share
? null
: selectMode == SelectMode.select
? [
if (controller.spaces.isNotEmpty)
IconButton(
tooltip: L10n.of(context)!.addToSpace,
icon: const Icon(Icons.group_work_outlined),
onPressed: controller.addOrRemoveToSpace,
),
IconButton(
tooltip: L10n.of(context)!.toggleUnread,
icon: Icon(controller.anySelectedRoomNotMarkedUnread
? Icons.mark_chat_read_outlined
: Icons.mark_chat_unread_outlined),
onPressed: controller.toggleUnread,
),
IconButton(
tooltip: L10n.of(context)!.toggleFavorite,
icon: Icon(controller.anySelectedRoomNotFavorite
? Icons.push_pin_outlined
: Icons.push_pin),
onPressed: controller.toggleFavouriteRoom,
),
IconButton(
icon: Icon(controller.anySelectedRoomNotMuted
? Icons.notifications_off_outlined
: Icons.notifications_outlined),
tooltip: L10n.of(context)!.toggleMuted,
onPressed: controller.toggleMuted,
),
IconButton(
icon: const Icon(Icons.delete_outlined),
tooltip: L10n.of(context)!.archive,
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<PopupMenuAction>(
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: [
const Icon(Icons.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<double> primaryAnimation,
Animation<double> 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));
})(),
),
);
}
@override
Size get preferredSize => const Size.fromHeight(56);
}

View File

@ -7,13 +7,12 @@ import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:snapping_sheet/snapping_sheet.dart'; import 'package:snapping_sheet/snapping_sheet.dart';
import 'package:vrouter/vrouter.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/chat_list.dart';
import 'package:fluffychat/pages/chat_list/client_chooser_button.dart';
import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart'; import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart';
import 'package:fluffychat/widgets/connection_status_header.dart'; import 'package:fluffychat/widgets/connection_status_header.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'chat_list_body.dart'; import 'chat_list_body.dart';
import 'chat_list_header.dart';
class ChatListView extends StatelessWidget { class ChatListView extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
@ -35,167 +34,7 @@ class ChatListView extends StatelessWidget {
if (selMode == SelectMode.select) redirector.stopRedirection(); if (selMode == SelectMode.select) redirector.stopRedirection();
}, },
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: ChatListHeader(controller: controller),
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,
),
centerTitle: false,
actions: selectMode == SelectMode.share
? null
: selectMode == SelectMode.select
? [
if (controller.spaces.isNotEmpty)
IconButton(
tooltip: L10n.of(context)!.addToSpace,
icon: const Icon(Icons.group_work_outlined),
onPressed: controller.addOrRemoveToSpace,
),
IconButton(
tooltip: L10n.of(context)!.toggleUnread,
icon: Icon(controller.anySelectedRoomNotMarkedUnread
? Icons.mark_chat_read_outlined
: Icons.mark_chat_unread_outlined),
onPressed: controller.toggleUnread,
),
IconButton(
tooltip: L10n.of(context)!.toggleFavorite,
icon: Icon(controller.anySelectedRoomNotFavorite
? Icons.push_pin_outlined
: Icons.push_pin),
onPressed: controller.toggleFavouriteRoom,
),
IconButton(
icon: Icon(controller.anySelectedRoomNotMuted
? Icons.notifications_off_outlined
: Icons.notifications_outlined),
tooltip: L10n.of(context)!.toggleMuted,
onPressed: controller.toggleMuted,
),
IconButton(
icon: const Icon(Icons.delete_outlined),
tooltip: L10n.of(context)!.archive,
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<PopupMenuAction>(
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: [
const Icon(Icons.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: Text(selectMode == SelectMode.share
? L10n.of(context)!.share
: selectMode == SelectMode.select
? controller.selectedRoomIds.length.toString()
: controller.activeSpaceId == null
? AppConfig.applicationName
: Matrix.of(context)
.client
.getRoomById(controller.activeSpaceId!)!
.displayname),
),
body: LayoutBuilder( body: LayoutBuilder(
builder: (context, size) { builder: (context, size) {
controller.snappingSheetContainerSize = size; controller.snappingSheetContainerSize = size;

View File

@ -123,6 +123,8 @@ class ClientChooserButton extends StatelessWidget {
onKeysPressed: () => _previousAccount(matrix), onKeysPressed: () => _previousAccount(matrix),
), ),
PopupMenuButton<Object>( PopupMenuButton<Object>(
child: Material(
borderRadius: BorderRadius.zero,
child: Avatar( child: Avatar(
mxContent: snapshot.data?.avatarUrl, mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ?? name: snapshot.data?.displayName ??
@ -130,6 +132,7 @@ class ClientChooserButton extends StatelessWidget {
size: 28, size: 28,
fontSize: 12, fontSize: 12,
), ),
),
onSelected: _clientSelected, onSelected: _clientSelected,
itemBuilder: _bundleMenuItems, itemBuilder: _bundleMenuItems,
), ),

View File

@ -12,6 +12,8 @@ import 'package:fluffychat/widgets/matrix.dart';
const kSpacesBottomBarHeight = 56.0; const kSpacesBottomBarHeight = 56.0;
final GlobalKey _globalKey = GlobalKey();
class SpacesBottomBar extends StatelessWidget { class SpacesBottomBar extends StatelessWidget {
final ChatListController controller; final ChatListController controller;
@ -37,13 +39,16 @@ class SpacesBottomBar extends StatelessWidget {
return SingleChildScrollView( return SingleChildScrollView(
controller: controller.snappingSheetScrollContentController, controller: controller.snappingSheetScrollContentController,
child: AnimatedBuilder( child: AnimatedBuilder(
child: _SpacesBottomNavigation(controller: controller), child: _SpacesBottomNavigation(
key: _globalKey, controller: controller),
builder: (context, child) { builder: (context, child) {
if (controller.snappingSheetContainerSize == null) { if (controller.snappingSheetContainerSize == null) {
return child!; return child!;
} }
final rawPosition = final rawPosition =
controller.snappingSheetController.currentPosition; controller.snappingSheetController.isAttached
? controller.snappingSheetController.currentPosition
: 0;
final position = rawPosition / final position = rawPosition /
controller.snappingSheetContainerSize!.maxHeight; controller.snappingSheetContainerSize!.maxHeight;

View File

@ -77,6 +77,14 @@ class AllRoomsSpacesEntry extends SpacesEntry {
@override @override
bool shouldShowStoriesHeader(BuildContext context) => true; bool shouldShowStoriesHeader(BuildContext context) => true;
@override
bool operator ==(Object other) {
return runtimeType == other.runtimeType;
}
@override
int get hashCode => runtimeType.hashCode;
} }
// "Direct Chats" entry. // "Direct Chats" entry.
@ -101,6 +109,14 @@ class DirectChatsSpacesEntry extends SpacesEntry {
@override @override
bool shouldShowStoriesHeader(BuildContext context) => true; bool shouldShowStoriesHeader(BuildContext context) => true;
@override
bool operator ==(Object other) {
return runtimeType == other.runtimeType;
}
@override
int get hashCode => runtimeType.hashCode;
} }
// "Groups" entry. // "Groups" entry.
@ -134,6 +150,14 @@ class GroupsSpacesEntry extends SpacesEntry {
bool separatedGroup(Room room, List<Room> spaces) { bool separatedGroup(Room room, List<Room> spaces) {
return !spaces.any((space) => _roomInsideSpace(room, space)); return !spaces.any((space) => _roomInsideSpace(room, space));
} }
@override
bool operator ==(Object other) {
return runtimeType == other.runtimeType;
}
@override
int get hashCode => runtimeType.hashCode;
} }
// All rooms associated with a specific space. // All rooms associated with a specific space.
@ -179,12 +203,12 @@ class SpaceSpacesEntry extends SpacesEntry {
@override @override
bool stillValid(BuildContext context) => bool stillValid(BuildContext context) =>
Matrix.of(context).client.getRoomById(space.id) != null; Matrix.of(context).client.getRoomById(space.id) != null;
@override
bool operator ==(Object other) {
return hashCode == other.hashCode;
} }
// Produces a "rough equivalence" for maintaining the current spaces index. @override
bool spacesEntryRoughEquivalence(SpacesEntry a, SpacesEntry b) { int get hashCode => space.id.hashCode;
if ((a is SpaceSpacesEntry) && (b is SpaceSpacesEntry)) {
return a.space.id == b.space.id;
}
return a == b;
} }

View File

@ -93,7 +93,8 @@ class SignupPageView extends StatelessWidget {
color: Colors.black, color: Colors.black,
), ),
hintText: L10n.of(context)!.enterAnEmailAddress, hintText: L10n.of(context)!.enterAnEmailAddress,
errorText: controller.error), errorText: controller.error,
),
), ),
), ),
Hero( Hero(

View File

@ -66,6 +66,7 @@ class ContentBanner extends StatelessWidget {
imageUrl: src.toString(), imageUrl: src.toString(),
height: 300, height: 300,
fit: BoxFit.cover, fit: BoxFit.cover,
errorWidget: (c, m, e) => Icon(defaultIcon, size: 200),
); );
}) })
: Icon(defaultIcon, size: 200), : Icon(defaultIcon, size: 200),