fix: minor design bugs

- fix spaces animation direction
- animate chat list app bar title
- fix ink splash radius in account selector
- fix missing asset fallback in content banner

Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>
This commit is contained in:
TheOneWithTheBraid 2022-05-21 12:09:25 +02:00
parent 487b3da979
commit 9f8a7f79f2
11 changed files with 285 additions and 194 deletions

View File

@ -896,7 +896,7 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"enterAnEmailAddress": "E-Mail-Adresse (nicht erforderlich)", "enterAnEmailAddress": "Gib eine E-Mail-Adresse ein",
"@enterAnEmailAddress": { "@enterAnEmailAddress": {
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}

View File

@ -955,13 +955,11 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"enterAnEmailAddress": "Email address (not mandatory)", "enterAnEmailAddress": "Enter an email address",
"@enterAnEmailAddress": { "@enterAnEmailAddress": {
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"emailContinueAnyway": "Continue anyway",
"emailEmptyWarning": "Warning: Even though adding a mail address leaks sensitive personal data, you have no way to recover your password without it.",
"enterASpacepName": "Enter a space name", "enterASpacepName": "Enter a space name",
"@enterASpacepName": {}, "@enterASpacepName": {},
"enterAUsername": "Enter a username", "enterAUsername": "Enter a username",

View File

@ -910,7 +910,7 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"enterAnEmailAddress": "Adresse de courriel (ne pas mandatoire)", "enterAnEmailAddress": "Saisissez une adresse de courriel",
"@enterAnEmailAddress": { "@enterAnEmailAddress": {
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}

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,11 +176,13 @@ 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
kSpacesBottomBarHeight .controller.snappingSheetController.currentPosition
? SharedAxisTransitionType.horizontal : 0) ==
: SharedAxisTransitionType.vertical, kSpacesBottomBarHeight
? SharedAxisTransitionType.horizontal
: SharedAxisTransitionType.vertical,
fillColor: Theme.of(context).scaffoldBackgroundColor, fillColor: Theme.of(context).scaffoldBackgroundColor,
child: child, child: child,
); );
@ -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,12 +123,15 @@ class ClientChooserButton extends StatelessWidget {
onKeysPressed: () => _previousAccount(matrix), onKeysPressed: () => _previousAccount(matrix),
), ),
PopupMenuButton<Object>( PopupMenuButton<Object>(
child: Avatar( child: Material(
mxContent: snapshot.data?.avatarUrl, borderRadius: BorderRadius.zero,
name: snapshot.data?.displayName ?? child: Avatar(
matrix.client.userID!.localpart, mxContent: snapshot.data?.avatarUrl,
size: 28, name: snapshot.data?.displayName ??
fontSize: 12, matrix.client.userID!.localpart,
size: 28,
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;
}
// Produces a "rough equivalence" for maintaining the current spaces index. @override
bool spacesEntryRoughEquivalence(SpacesEntry a, SpacesEntry b) { bool operator ==(Object other) {
if ((a is SpaceSpacesEntry) && (b is SpaceSpacesEntry)) { return hashCode == other.hashCode;
return a.space.id == b.space.id;
} }
return a == b;
@override
int get hashCode => space.id.hashCode;
} }

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),