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) {
if (snappingSheetController.currentPosition != kSpacesBottomBarHeight) {
if ((snappingSheetController.isAttached
? snappingSheetController.currentPosition
: 0) !=
kSpacesBottomBarHeight) {
snapBackSpacesSheet();
}
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_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 '../../utils/stream_extension.dart';
import '../../widgets/matrix.dart';
@ -28,7 +29,7 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
// used to check the animation direction
String? _lastUserId;
String? _lastSpaceId;
SpacesEntry? _lastSpace;
@override
void initState() {
@ -175,11 +176,13 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType:
widget.controller.snappingSheetController.currentPosition ==
kSpacesBottomBarHeight
? SharedAxisTransitionType.horizontal
: SharedAxisTransitionType.vertical,
transitionType: (widget.controller.snappingSheetController.isAttached
? widget
.controller.snappingSheetController.currentPosition
: 0) ==
kSpacesBottomBarHeight
? SharedAxisTransitionType.horizontal
: SharedAxisTransitionType.vertical,
fillColor: Theme.of(context).scaffoldBackgroundColor,
child: child,
);
@ -208,13 +211,13 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
}
// otherwise, the space changed...
else {
reversed = widget.controller.spaces
.indexWhere((element) => element.id == _lastSpaceId) <
widget.controller.spaces.indexWhere(
(element) => element.id == widget.controller.activeSpaceId);
reversed = widget.controller.spacesEntries
.indexWhere((element) => element == _lastSpace) <
widget.controller.spacesEntries.indexWhere(
(element) => element == widget.controller.activeSpacesEntry);
}
_lastUserId = newClient.userID;
_lastSpaceId = widget.controller.activeSpaceId;
_lastSpace = widget.controller.activeSpacesEntry;
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: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 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart';
import 'package:fluffychat/widgets/connection_status_header.dart';
import '../../widgets/matrix.dart';
import 'chat_list_body.dart';
import 'chat_list_header.dart';
class ChatListView extends StatelessWidget {
final ChatListController controller;
@ -35,167 +34,7 @@ class ChatListView extends StatelessWidget {
if (selMode == SelectMode.select) redirector.stopRedirection();
},
child: Scaffold(
appBar: 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: 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),
),
appBar: ChatListHeader(controller: controller),
body: LayoutBuilder(
builder: (context, size) {
controller.snappingSheetContainerSize = size;

View File

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

View File

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

View File

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

View File

@ -88,12 +88,13 @@ class SignupPageView extends StatelessWidget {
controller.loading ? null : [AutofillHints.username],
validator: controller.emailTextFieldValidator,
decoration: FluffyThemes.loginTextFieldDecoration(
prefixIcon: const Icon(
Icons.mail_outlined,
color: Colors.black,
),
hintText: L10n.of(context)!.enterAnEmailAddress,
errorText: controller.error),
prefixIcon: const Icon(
Icons.mail_outlined,
color: Colors.black,
),
hintText: L10n.of(context)!.enterAnEmailAddress,
errorText: controller.error,
),
),
),
Hero(

View File

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