feat: improve spaces

- support to show spaces in a list
- add a beautiful animation

This MR makes Spaces much easier to use on desktops and allows to better
find the right space in case they have no avatar.

There will be another MR builting on this work as soon as
https://gitlab.com/famedly/company/frontend/libraries/matrix_api_lite/-/merge_requests/58
is merged.

Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>
This commit is contained in:
TheOneWithTheBraid 2022-04-02 16:18:36 +02:00
parent 42267f263e
commit 26983a15a8
8 changed files with 499 additions and 280 deletions

View File

@ -1311,6 +1311,7 @@
"type": "text",
"placeholders": {}
},
"showSpaces": "Show spaces list",
"loadMore": "Load more…",
"@loadMore": {
"type": "text",

View File

@ -8,11 +8,13 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:snapping_sheet/snapping_sheet.dart';
import 'package:uni_links/uni_links.dart';
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/platform_infos.dart';
@ -40,7 +42,7 @@ class ChatList extends StatefulWidget {
ChatListController createState() => ChatListController();
}
class ChatListController extends State<ChatList> {
class ChatListController extends State<ChatList> with TickerProviderStateMixin {
StreamSubscription? _intentDataStreamSubscription;
StreamSubscription? _intentFileStreamSubscription;
@ -54,6 +56,8 @@ class ChatListController extends State<ChatList> {
return (id == null || !id.stillValid(context)) ? defaultSpacesEntry : id;
}
BoxConstraints? snappingSheetContainerSize;
String? get activeSpaceId => activeSpacesEntry.getSpace(context)?.id;
final ScrollController scrollController = ScrollController();
@ -61,6 +65,10 @@ class ChatListController extends State<ChatList> {
final StreamController<Client> _clientStream = StreamController.broadcast();
SnappingSheetController snappingSheetController = SnappingSheetController();
ScrollController snappingSheetScrollContentController = ScrollController();
Stream<Client> get clientStream => _clientStream.stream;
void _onScroll() {
@ -72,7 +80,10 @@ class ChatListController extends State<ChatList> {
}
}
void setActiveSpacesEntry(BuildContext context, SpacesEntry spaceId) {
void setActiveSpacesEntry(BuildContext context, SpacesEntry? spaceId) {
if (snappingSheetController.currentPosition != kSpacesBottomBarHeight) {
snapBackSpacesSheet();
}
setState(() => _activeSpacesEntry = spaceId);
}
@ -480,6 +491,8 @@ class ChatListController extends State<ChatList> {
VRouter.of(context).to('/rooms');
setState(() {
_activeSpacesEntry = null;
snappingSheetController = SnappingSheetController();
snappingSheetScrollContentController = ScrollController();
selectedRoomIds.clear();
Matrix.of(context).setActiveClient(client);
});
@ -575,6 +588,21 @@ class ChatListController extends State<ChatList> {
void _hackyWebRTCFixForWeb() {
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),
);
}
}
enum EditBundleAction { addToBundle, removeFromBundle }

View File

@ -8,6 +8,7 @@ import 'package:animations/animations.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import 'package:snapping_sheet/snapping_sheet.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/config/app_config.dart';
@ -28,203 +29,240 @@ class ChatListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<Object?>(
stream: Matrix.of(context).onShareContentChanged.stream,
builder: (_, __) {
final selectMode = controller.selectMode;
return VWidgetGuard(
onSystemPop: (redirector) async {
final selMode = controller.selectMode;
if (selMode != SelectMode.normal) controller.cancelAction();
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
stream: Matrix.of(context).onShareContentChanged.stream,
builder: (_, __) {
final selectMode = controller.selectMode;
final showSpaces =
controller.spaces.isNotEmpty && controller.selectedRoomIds.isEmpty;
return VWidgetGuard(
onSystemPop: (redirector) async {
final selMode = controller.selectMode;
if (selMode != SelectMode.normal) controller.cancelAction();
if (selMode == SelectMode.select) redirector.stopRedirection();
},
child: Scaffold(
appBar: AppBar(
elevation: controller.scrolledToTop ? 0 : null,
actionsIconTheme: IconThemeData(
color: controller.selectedRoomIds.isEmpty
? 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),
: Theme.of(context).colorScheme.primary,
),
body: 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,
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(
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,
),
),
),
title: Text(L10n.of(context)!.setupChatBackupNow),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.firstRunBootstrapAction,
),
Expanded(child: _ChatListViewBody(controller)),
],
),
),
Expanded(child: _ChatListViewBody(controller)),
]),
floatingActionButton: selectMode == SelectMode.normal
? KeyBoardShortcuts(
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,
);
},
),
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: () =>
@ -239,20 +277,14 @@ class ChatListView extends StatelessWidget {
onKeysPressed: () =>
VRouter.of(context).to('/newprivatechat'),
helpLabel: L10n.of(context)!.newChat,
)
: null,
bottomNavigationBar: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ConnectionStatusHeader(),
if (controller.spaces.isNotEmpty &&
controller.selectedRoomIds.isEmpty)
SpacesBottomBar(controller),
],
),
),
);
});
),
)
: null,
bottomNavigationBar: const ConnectionStatusHeader(),
),
);
},
);
}
}

View File

@ -1,53 +1,135 @@
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;
class SpacesBottomBar extends StatelessWidget {
final ChatListController controller;
const SpacesBottomBar(this.controller, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final foundIndex = controller.spacesEntries.indexWhere(
(se) => spacesEntryRoughEquivalence(se, controller.activeSpacesEntry));
final currentIndex = foundIndex == -1 ? 0 : foundIndex;
return Material(
color: Theme.of(context).appBarTheme.backgroundColor,
color: Theme.of(context).navigationBarTheme.backgroundColor,
elevation: 6,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppConfig.borderRadius)),
clipBehavior: Clip.hardEdge,
child: SafeArea(
child: StreamBuilder<Object>(
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 Container(
height: 56,
alignment: Alignment.center,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SalomonBottomBar(
itemPadding: const EdgeInsets.all(8),
currentIndex: currentIndex,
onTap: (i) => controller.setActiveSpacesEntry(
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(controller: controller),
builder: (context, child) {
if (controller.snappingSheetContainerSize == null) {
return child!;
}
final rawPosition =
controller.snappingSheetController.currentPosition;
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.activeSpaceId == null
? 1
: controller.spaces
.indexWhere((space) => controller.activeSpaceId == space.id) +
2;
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()
: i == 1
? controller.setActiveSpacesEntry(
context,
null,
)
: controller.setActiveSpacesEntry(
context,
controller.spacesEntries[i],
),
selectedItemColor: Theme.of(context).colorScheme.primary,
items: controller.spacesEntries
.map((entry) => _buildSpacesEntryUI(context, entry))
.toList(),
),
),
);
}),
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(),
],
),
),
);
}

View File

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'chat_list.dart';
class SpacesDrawer extends StatelessWidget {
final ChatListController controller;
const SpacesDrawer({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
final currentIndex = controller.activeSpaceId == null
? 0
: controller.spaces
.indexWhere((space) => controller.activeSpaceId == space.id) +
1;
final Map<SpacesEntry, dynamic> spaceHierarchy =
Map.fromEntries(controller.spacesEntries.map((e) => MapEntry(e, null)));
// 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: ListView.builder(
shrinkWrap: true,
itemCount: spaceHierarchy.length,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return ListTile(
selected: currentIndex == index,
leading: const Icon(Icons.keyboard_arrow_down),
title: Text(L10n.of(context)!.allChats),
onTap: () => controller.setActiveSpacesEntry(
context,
null,
),
);
} else {
final space = spaceHierarchy.keys.toList()[index];
final room = space.getSpace(context)!;
return ListTile(
selected: currentIndex == index,
leading: Avatar(
mxContent: room.avatar,
name: space.getName(context),
size: 24,
fontSize: 12,
),
title: Text(space.getName(context)),
subtitle: room.topic.isEmpty
? null
: Tooltip(
message: room.topic,
child: Text(
room.topic.replaceAll('\n', ' '),
softWrap: false,
overflow: TextOverflow.fade,
),
),
onTap: () => controller.setActiveSpacesEntry(
context,
space,
),
trailing: IconButton(
icon: const Icon(Icons.edit),
tooltip: L10n.of(context)!.edit,
onPressed: () => controller.editSpace(context, room.id),
),
);
}
},
),
);
}
}

View File

@ -12,7 +12,6 @@ import desktop_lifecycle
import device_info_plus_macos
import emoji_picker_flutter
import file_selector_macos
import flutter_app_badger
import flutter_local_notifications
import flutter_secure_storage_macos
import flutter_web_auth
@ -36,7 +35,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStorageMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageMacosPlugin"))
FlutterWebAuthPlugin.register(with: registry.registrar(forPlugin: "FlutterWebAuthPlugin"))

View File

@ -14,7 +14,7 @@ packages:
name: adaptive_dialog
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0+1"
version: "1.4.0"
adaptive_theme:
dependency: "direct main"
description:
@ -289,14 +289,14 @@ packages:
name: dart_webrtc
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.0.4"
dbus:
dependency: "direct overridden"
description:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.3"
version: "0.7.2"
desktop_drop:
dependency: "direct main"
description:
@ -483,7 +483,7 @@ packages:
name: flutter_app_badger
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
version: "1.3.0"
flutter_app_lock:
dependency: "direct main"
description:
@ -551,7 +551,7 @@ packages:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "9.4.0"
version: "9.4.1"
flutter_local_notifications_linux:
dependency: transitive
description:
@ -598,7 +598,7 @@ packages:
name: flutter_native_splash
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
version: "2.1.2+1"
flutter_olm:
dependency: "direct main"
description:
@ -626,7 +626,7 @@ packages:
name: flutter_ringtone_player
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.0"
version: "3.1.1"
flutter_secure_storage:
dependency: "direct main"
description:
@ -713,7 +713,7 @@ packages:
name: flutter_webrtc
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.5"
version: "0.8.4"
frontend_server_client:
dependency: transitive
description:
@ -851,13 +851,6 @@ packages:
name: image_picker
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.5"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.4+11"
image_picker_for_web:
dependency: transitive
@ -866,13 +859,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.6"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.4+11"
image_picker_platform_interface:
dependency: transitive
description:
@ -957,13 +943,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.1"
lint:
dependency: transitive
description:
name: lint
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
lints:
dependency: transitive
description:
@ -1140,14 +1119,14 @@ packages:
name: package_info_plus
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.2"
version: "1.4.0"
package_info_plus_linux:
dependency: transitive
description:
name: package_info_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.0.3"
package_info_plus_macos:
dependency: transitive
description:
@ -1168,14 +1147,14 @@ packages:
name: package_info_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.0.4"
package_info_plus_windows:
dependency: transitive
description:
name: package_info_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
version: "1.0.4"
path:
dependency: transitive
description:
@ -1273,7 +1252,7 @@ packages:
name: permission_handler_apple
url: "https://pub.dartlang.org"
source: hosted
version: "9.0.4"
version: "9.0.3"
permission_handler_platform_interface:
dependency: transitive
description:
@ -1420,7 +1399,7 @@ packages:
name: record
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.4"
version: "3.0.3"
record_platform_interface:
dependency: transitive
description:
@ -1539,7 +1518,7 @@ packages:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.2.0"
shelf_packages_handler:
dependency: transitive
description:
@ -1573,6 +1552,15 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
snapping_sheet:
dependency: "direct main"
description:
path: "."
ref: listenable
resolved-ref: "3da78eea5d222baa1b266c19284acafee090f6be"
url: "https://github.com/TheOneWithTheBraid/snapping_sheet.git"
source: git
version: "3.1.0"
source_map_stack_trace:
dependency: transitive
description:
@ -1607,7 +1595,7 @@ packages:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
version: "2.2.0"
stack_trace:
dependency: transitive
description:
@ -1831,7 +1819,7 @@ packages:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.9"
version: "2.0.6"
url_launcher_windows:
dependency: transitive
description:
@ -1999,14 +1987,14 @@ packages:
name: webrtc_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
version: "1.0.2"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.1"
version: "2.5.0"
wkt_parser:
dependency: transitive
description:
@ -2037,4 +2025,4 @@ packages:
version: "3.1.0"
sdks:
dart: ">=2.16.1 <3.0.0"
flutter: ">=2.10.0"
flutter: ">=2.8.0"

View File

@ -78,6 +78,7 @@ dependencies:
share: ^2.0.4
shared_preferences: ^2.0.13
slugify: ^2.0.0
snapping_sheet: ^3.1.0
swipe_to_action: ^0.2.0
uni_links: ^0.5.1
unifiedpush: ^4.0.0
@ -146,3 +147,9 @@ dependency_overrides:
url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git
ref: null-safety
provider: 5.0.0
# wating for `Listenable` implementation
# Upstream pull request: https://github.com/AdamJonsson/snapping_sheet/pull/84
snapping_sheet:
git:
url: https://github.com/TheOneWithTheBraid/snapping_sheet.git
ref: listenable