forked from Nexus/nexus
Compare commits
11 commits
c10c74a1f3
...
e1d7a30a06
| Author | SHA1 | Date | |
|---|---|---|---|
|
e1d7a30a06 |
|||
|
0c61623a94 |
|||
|
0a9b71230b |
|||
|
15c057707f |
|||
|
9cc18e16b8 |
|||
|
240984a832 |
|||
|
633e83b915 |
|||
|
c5122fc34f |
|||
|
457de3c77c |
|||
|
c9aa0173d8 |
|||
|
22aa26fd05 |
10 changed files with 246 additions and 216 deletions
|
|
@ -8,12 +8,13 @@ import "package:nexus/models/content/create.dart";
|
|||
import "package:nexus/models/content/power_levels.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
|
||||
class MembersGroupedController extends AsyncNotifier<IMap<int?, ISet<Event>>> {
|
||||
class MembersGroupedController
|
||||
extends AsyncNotifier<IList<MapEntry<int?, ISet<Event>>>> {
|
||||
final MembersByStatusConfig config;
|
||||
MembersGroupedController(this.config);
|
||||
|
||||
@override
|
||||
Future<IMap<int?, ISet<Event>>> build() async {
|
||||
Future<IList<MapEntry<int?, ISet<Event>>>> build() async {
|
||||
final room = ref.watch(
|
||||
RoomsController.provider.select((value) => value[config.roomId]),
|
||||
);
|
||||
|
|
@ -42,7 +43,8 @@ class MembersGroupedController extends AsyncNotifier<IMap<int?, ISet<Event>>> {
|
|||
MembersByStatusController.provider(config).future,
|
||||
);
|
||||
|
||||
return members.fold<IMap<int?, ISet<Event>>>(.new(), (result, event) {
|
||||
return members
|
||||
.fold<IMap<int?, ISet<Event>>>(.new(), (result, event) {
|
||||
final groupKey = creators?.contains(event.stateKey!) == true
|
||||
? null
|
||||
: content.users[event.stateKey!] ?? content.usersDefault;
|
||||
|
|
@ -52,13 +54,17 @@ class MembersGroupedController extends AsyncNotifier<IMap<int?, ISet<Event>>> {
|
|||
(value) => value.add(event),
|
||||
ifAbsent: () => .new({event}),
|
||||
);
|
||||
});
|
||||
})
|
||||
.toEntryIList(
|
||||
compare: (a, b) =>
|
||||
(b?.key ?? double.infinity).compareTo(a?.key ?? double.infinity),
|
||||
);
|
||||
}
|
||||
|
||||
static final provider =
|
||||
AsyncNotifierProvider.family<
|
||||
MembersGroupedController,
|
||||
IMap<int?, ISet<Event>>,
|
||||
IList<MapEntry<int?, ISet<Event>>>,
|
||||
MembersByStatusConfig
|
||||
>(MembersGroupedController.new);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import "package:nexus/controllers/account_data_controller.dart";
|
|||
import "package:nexus/controllers/rooms_controller.dart";
|
||||
import "package:nexus/controllers/top_level_spaces_controller.dart";
|
||||
import "package:nexus/controllers/space_edges_controller.dart";
|
||||
import "package:nexus/models/room.dart";
|
||||
import "package:nexus/models/space.dart";
|
||||
import "package:nexus/models/subspace.dart";
|
||||
|
||||
class SpacesController extends Notifier<IList<Space>> {
|
||||
@override
|
||||
|
|
@ -14,91 +16,97 @@ class SpacesController extends Notifier<IList<Space>> {
|
|||
final rooms = ref.watch(RoomsController.provider);
|
||||
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
|
||||
final spaceEdges = ref.watch(SpaceEdgesController.provider);
|
||||
final accountData = ref.watch(AccountDataController.provider);
|
||||
|
||||
final childRoomsBySpaceId = IMap.fromEntries(
|
||||
topLevelSpaceIds.map((spaceId) {
|
||||
ISet<String> walk(String currentId) {
|
||||
final children = spaceEdges[currentId] ?? .new();
|
||||
final childrenById = {
|
||||
for (final entry in spaceEdges.entries)
|
||||
entry.key: entry.value.map((e) => e.childId).toList(),
|
||||
};
|
||||
|
||||
return children.fold<ISet<String>>(.new(), (acc, edge) {
|
||||
final childId = edge.childId;
|
||||
final isSpace = spaceEdges.containsKey(childId);
|
||||
Set<String> collectDescendants(String startId) {
|
||||
final visited = <String>{};
|
||||
final stack = [startId];
|
||||
|
||||
return acc
|
||||
.addAll(!isSpace ? ISet([childId]) : const .empty())
|
||||
.addAll(isSpace ? walk(childId) : const .empty());
|
||||
});
|
||||
while (stack.isNotEmpty) {
|
||||
final current = stack.removeLast();
|
||||
final children = childrenById[current] ?? const [];
|
||||
|
||||
for (final child in children) {
|
||||
if (visited.add(child)) {
|
||||
stack.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MapEntry(
|
||||
spaceId,
|
||||
walk(spaceId).map((id) => rooms[id]).nonNulls.toIList(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
return visited;
|
||||
}
|
||||
|
||||
final allNestedRoomIds = childRoomsBySpaceId.values
|
||||
.expand((l) => l)
|
||||
.map(
|
||||
(room) => rooms.entries
|
||||
.firstWhere(
|
||||
(entry) => entry.value.metadata?.id == room.metadata?.id,
|
||||
)
|
||||
.key,
|
||||
)
|
||||
.toISet();
|
||||
Space buildSpace(String spaceId) {
|
||||
final space = rooms[spaceId];
|
||||
final directChildrenIds = childrenById[spaceId] ?? const [];
|
||||
|
||||
final directRooms = <Room>[];
|
||||
final subSpaces = <Subspace>[];
|
||||
|
||||
for (final childId in directChildrenIds) {
|
||||
final room = rooms[childId];
|
||||
if (room == null) continue;
|
||||
|
||||
if (childrenById.containsKey(childId)) {
|
||||
final descendants = collectDescendants(childId);
|
||||
|
||||
subSpaces.add(
|
||||
.new(
|
||||
room: room,
|
||||
children: .new(descendants.map((id) => rooms[id]).nonNulls),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
directRooms.add(room);
|
||||
}
|
||||
}
|
||||
|
||||
return .new(
|
||||
id: spaceId,
|
||||
room: space,
|
||||
title: space?.metadata?.name ?? "Unnamed Space",
|
||||
children: .new(directRooms),
|
||||
subSpaces: .new(subSpaces),
|
||||
);
|
||||
}
|
||||
|
||||
final spaces = topLevelSpaceIds.map(buildSpace).toIList();
|
||||
|
||||
final usedRoomIds = {
|
||||
for (final space in spaces) ...[
|
||||
...space.children.map((r) => r.metadata?.id),
|
||||
...space.subSpaces.expand((s) => s.children.map((r) => r.metadata?.id)),
|
||||
],
|
||||
}.nonNulls.toISet();
|
||||
|
||||
final directMessages = IMap(
|
||||
accountData["m.direct"]?.content ?? {},
|
||||
).values.expand((e) => e).toISet();
|
||||
|
||||
final otherRooms = rooms.entries
|
||||
.where(
|
||||
(e) =>
|
||||
!allNestedRoomIds.contains(e.key) &&
|
||||
!usedRoomIds.contains(e.key) &&
|
||||
!topLevelSpaceIds.contains(e.key) &&
|
||||
!spaceEdges.containsKey(e.key),
|
||||
!childrenById.containsKey(e.key),
|
||||
)
|
||||
.map((e) => e.value);
|
||||
|
||||
final accountData = ref.watch(AccountDataController.provider);
|
||||
|
||||
final directMessages = IMap(
|
||||
accountData["m.direct"]?.content ?? {},
|
||||
).values.expand((element) => element);
|
||||
.map((e) => e.value)
|
||||
.toIList();
|
||||
|
||||
final homeRooms = otherRooms
|
||||
.where(
|
||||
(room) =>
|
||||
directMessages.any(
|
||||
(directMessage) => directMessage == room.metadata?.id,
|
||||
) ==
|
||||
false,
|
||||
)
|
||||
.where((r) => !directMessages.contains(r.metadata?.id))
|
||||
.toIList();
|
||||
|
||||
final dmRooms = otherRooms
|
||||
.where(
|
||||
(room) => directMessages.any(
|
||||
(directMessage) => directMessage == room.metadata?.id,
|
||||
),
|
||||
)
|
||||
.where((r) => directMessages.contains(r.metadata?.id))
|
||||
.toIList();
|
||||
|
||||
final topLevelSpacesList = topLevelSpaceIds
|
||||
.map((id) {
|
||||
final room = rooms[id];
|
||||
if (room == null) return null;
|
||||
|
||||
final children = childRoomsBySpaceId[id] ?? .new();
|
||||
return Space(
|
||||
subSpaces: const IList.empty(), // TODO
|
||||
id: id,
|
||||
title: room.metadata?.name ?? "Unnamed Room",
|
||||
room: room,
|
||||
children: children,
|
||||
);
|
||||
})
|
||||
.nonNulls
|
||||
.toIList();
|
||||
|
||||
return <Space>[
|
||||
final allSpaces = <Space>[
|
||||
.new(
|
||||
id: "home",
|
||||
title: "Home",
|
||||
|
|
@ -113,8 +121,10 @@ class SpacesController extends Notifier<IList<Space>> {
|
|||
children: dmRooms,
|
||||
subSpaces: .new(),
|
||||
),
|
||||
...topLevelSpacesList,
|
||||
]
|
||||
...spaces,
|
||||
];
|
||||
|
||||
return allSpaces
|
||||
.map(
|
||||
(space) => space.copyWith(
|
||||
children: .new(
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ class LoginPage extends HookConsumerWidget {
|
|||
validator: requiredValidator,
|
||||
controller: password,
|
||||
obscureText: true,
|
||||
onFieldSubmitted: (_) => tryLogin(),
|
||||
// Don't defocus on submit
|
||||
onEditingComplete: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,22 @@ class VerifyPage extends HookConsumerWidget {
|
|||
final inputError = useState<String?>(null);
|
||||
final formKey = useRef(GlobalKey<FormState>());
|
||||
|
||||
Future<void> verify() async {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
if (formKey.value.currentState?.validate() != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputError.value = await ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.verify(passphraseController.text);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: Appbar(),
|
||||
body: AlertDialog(
|
||||
|
|
@ -40,29 +56,16 @@ class VerifyPage extends HookConsumerWidget {
|
|||
label: Text("Recovery Key or Passphrase"),
|
||||
errorText: inputError.value,
|
||||
),
|
||||
onFieldSubmitted: (_) => verify(),
|
||||
// Don't defocus on submit
|
||||
onEditingComplete: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: isLoading.value
|
||||
? null
|
||||
: () async {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
if (formKey.value.currentState?.validate() != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputError.value = await ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.verify(passphraseController.text);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
},
|
||||
onPressed: isLoading.value ? null : verify,
|
||||
child: Text("Verify"),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ class MentionChip extends ConsumerWidget {
|
|||
child: IgnorePointer(
|
||||
child: Chip(
|
||||
label: Text(
|
||||
(membership == null ? null : "@${membership.displayName}") ??
|
||||
(membership?.displayName == null
|
||||
? null
|
||||
: "@${membership!.displayName}") ??
|
||||
mention,
|
||||
style: .new(
|
||||
fontWeight: .bold,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import "package:flutter/material.dart";
|
|||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:m3e_buttons/m3e_buttons.dart";
|
||||
import "package:material_segmented_list/material_segmented_list.dart";
|
||||
import "package:m3e_card_list/m3e_card_list.dart";
|
||||
import "package:nexus/controllers/members_by_status_controller.dart";
|
||||
import "package:nexus/controllers/members_grouped_controller.dart";
|
||||
import "package:nexus/helpers/extensions/get_localpart.dart";
|
||||
import "package:nexus/helpers/extensions/string_to_color.dart";
|
||||
|
|
@ -20,31 +21,26 @@ class MemberList extends HookConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final status = useState(MembershipStatus.join);
|
||||
|
||||
final membersData = ref.watch(
|
||||
MembersGroupedController.provider(
|
||||
.new(roomId: roomId, status: status.value),
|
||||
),
|
||||
);
|
||||
final statusIndex = useState(0);
|
||||
|
||||
final options = <String, MembershipStatus>{
|
||||
"Joined": .join,
|
||||
"Invited": .invite,
|
||||
"Banned": .ban,
|
||||
};
|
||||
final status = options.values.toIList()[statusIndex.value];
|
||||
|
||||
return Drawer(
|
||||
shape: Border(),
|
||||
child: Column(
|
||||
children: [
|
||||
if (Scaffold.of(context).hasEndDrawer)
|
||||
AppBar(
|
||||
scrolledUnderElevation: 0,
|
||||
leading: Icon(Icons.people),
|
||||
title: Text("Members"),
|
||||
actionsPadding: .only(right: 4),
|
||||
actions: [
|
||||
if (Scaffold.of(context).hasEndDrawer)
|
||||
IconButton(
|
||||
onPressed: Scaffold.of(context).closeEndDrawer,
|
||||
icon: Icon(Icons.close),
|
||||
|
|
@ -52,18 +48,18 @@ class MemberList extends HookConsumerWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
M3EToggleButtonGroup(
|
||||
type: .connected,
|
||||
selectedIndex: options.values.toIList().indexOf(status.value),
|
||||
Padding(
|
||||
padding: .symmetric(vertical: 8),
|
||||
child: M3EToggleButtonGroup(
|
||||
selectedIndex: statusIndex.value,
|
||||
onSelectedIndexChanged: (index) =>
|
||||
status.value = options.values.elementAt(index ?? 0),
|
||||
// overflow: M3EButtonGroupOverflow.menu,
|
||||
statusIndex.value = index ?? statusIndex.value,
|
||||
actions: options
|
||||
.mapTo(
|
||||
(name, value) => M3EToggleButtonGroupAction(
|
||||
checkedLabel: Text(
|
||||
"$name${switch (membersData) {
|
||||
AsyncData(:final value) || AsyncLoading(:final value?) => " (${value.values.expand((element) => element).length})",
|
||||
"$name${switch (ref.watch(MembersByStatusController.provider(.new(roomId: roomId, status: value)))) {
|
||||
AsyncData(:final value) || AsyncLoading(:final value?) => " (${value.length})",
|
||||
_ => "",
|
||||
}}",
|
||||
),
|
||||
|
|
@ -72,8 +68,13 @@ class MemberList extends HookConsumerWidget {
|
|||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
|
||||
switch (membersData) {
|
||||
switch (ref.watch(
|
||||
MembersGroupedController.provider(
|
||||
.new(roomId: roomId, status: status),
|
||||
),
|
||||
)) {
|
||||
AsyncError(:final error, :final stackTrace) => ErrorDialog(
|
||||
error,
|
||||
stackTrace,
|
||||
|
|
@ -84,71 +85,76 @@ class MemberList extends HookConsumerWidget {
|
|||
child: Padding(
|
||||
padding: .symmetric(vertical: 18),
|
||||
child: Text(
|
||||
"No ${options.keys.toIList()[options.values.toIList().indexOf(status.value)]} Members",
|
||||
"No ${options.keys.toIList()[statusIndex.value]} Members",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Expanded(
|
||||
child: ListView(
|
||||
padding: .all(12),
|
||||
children: [
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
for (final MapEntry(key: powerLevel, value: members)
|
||||
in value.toEntryIList(
|
||||
compare: (a, b) => (b?.key ?? double.infinity)
|
||||
.compareTo(a?.key ?? double.infinity),
|
||||
)) ...[
|
||||
Padding(
|
||||
padding: .symmetric(horizontal: 4),
|
||||
in value) ...[
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: .symmetric(horizontal: 16),
|
||||
child: DividerText(
|
||||
powerLevel == null
|
||||
? "Creators"
|
||||
: "Power Level $powerLevel",
|
||||
),
|
||||
),
|
||||
SegmentedListSection(
|
||||
children: members
|
||||
.map(
|
||||
(member) => switch (member.content) {
|
||||
),
|
||||
SliverM3ECardList(
|
||||
padding: .all(4),
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHigh,
|
||||
margin: .symmetric(horizontal: 12, vertical: 4),
|
||||
itemCount: members.length,
|
||||
itemBuilder: (context, index) =>
|
||||
switch (members[index].content) {
|
||||
MembershipContent(
|
||||
:final avatarUrl,
|
||||
:final displayName,
|
||||
) =>
|
||||
SegmentedListTile(
|
||||
onTap: () {},
|
||||
// context.showUserPopover(
|
||||
// member.content as MembershipContent,
|
||||
// member.stateKey!,
|
||||
// roomId: roomId,
|
||||
// globalPosition: details.globalPosition,
|
||||
// ),
|
||||
ListTile(
|
||||
title: Text(
|
||||
displayName ??
|
||||
member.stateKey!.localpart,
|
||||
members[index]
|
||||
.stateKey!
|
||||
.localpart,
|
||||
overflow: .ellipsis,
|
||||
style: .new(
|
||||
color: member.stateKey!.colorHash,
|
||||
color: members[index]
|
||||
.stateKey!
|
||||
.colorHash,
|
||||
fontWeight: .bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
member.stateKey!,
|
||||
members[index].stateKey!,
|
||||
overflow: .ellipsis,
|
||||
),
|
||||
leading: AvatarOrHash(
|
||||
avatarUrl,
|
||||
displayName ??
|
||||
member.sender.localpart,
|
||||
members[index].sender.localpart,
|
||||
),
|
||||
),
|
||||
_ => throw Exception(
|
||||
"Member content was not MembershipContent",
|
||||
),
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
onTap: (index) {
|
||||
// context.showUserPopover(
|
||||
// member.content as MembershipContent,
|
||||
// member.stateKey!,
|
||||
// roomId: roomId,
|
||||
// globalPosition: details.globalPosition,
|
||||
// ),
|
||||
},
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class Sidebar extends HookConsumerWidget {
|
|||
.toList();
|
||||
|
||||
return Drawer(
|
||||
width: 340,
|
||||
width: 330,
|
||||
shape: Border(),
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
@ -206,19 +206,20 @@ class Sidebar extends HookConsumerWidget {
|
|||
],
|
||||
),
|
||||
child: NavigationRailM3E(
|
||||
expandedWidth: 360,
|
||||
expandedWidth: double.infinity,
|
||||
scrollable: true,
|
||||
background: Colors.transparent,
|
||||
type: .alwaysExpand,
|
||||
selectedIndex: selectedRoomIndex ?? 0,
|
||||
sections: [
|
||||
.new(
|
||||
header: selectedSpace.room == null ? null : Text("Rooms"),
|
||||
destinations: roomsToDestinations(selectedSpace.children),
|
||||
),
|
||||
for (final subSpace in selectedSpace.subSpaces)
|
||||
.new(
|
||||
header: Text(
|
||||
subSpace.room.metadata?.name ?? "Unnamed Room",
|
||||
subSpace.room.metadata?.name ?? "Unnamed Space",
|
||||
),
|
||||
destinations: roomsToDestinations(subSpace.children),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ flutter.buildFlutterApplication {
|
|||
window_size = "sha256-XelNtp7tpZ91QCEcvewVphNUtgQX7xrp5QP0oFo6DgM=";
|
||||
emoji_text_field = "sha256-3TOys09EP2GRo6pUBGPXaqBlE39O2Cmwt42Hs1cTDKo=";
|
||||
linkify = "sha256-mxV/XHLxF9cn7sUPr2SUNjVmDr5lbxkuGCbNdyiZi2c=";
|
||||
navigation_rail_m3e = "sha256-Zkqo8RkNSWiELB+Bk6iLsgft9THuJiMtysEwXNS/ZgY=";
|
||||
navigation_rail_m3e = "sha256-+2awDTQnK58gGRY1nuHckG/jjxarsYSRu9ovR4i4TEc=";
|
||||
};
|
||||
|
||||
postInstall = ''
|
||||
|
|
|
|||
18
pubspec.lock
18
pubspec.lock
|
|
@ -783,6 +783,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.3"
|
||||
m3e_card_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: m3e_card_list
|
||||
sha256: d4aba0123cccda40ac80789befa8d355e1dc16aa7dcee910157690b0546d78d6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
m3e_design:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -807,14 +815,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
material_segmented_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_segmented_list
|
||||
sha256: "384bfd41a78e745397ceff1dd39700961e6a5419ad911d1797bcc13ea3824241"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
measure_size:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -993,7 +993,7 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ dependencies:
|
|||
intl: 0.20.2
|
||||
fast_immutable_collections: 11.2.0
|
||||
path_provider: 2.1.5
|
||||
path_provider_android: 2.2.23
|
||||
url_launcher: 6.3.2
|
||||
freezed_annotation: 3.1.0
|
||||
image_picker: 1.2.2
|
||||
|
|
@ -62,12 +61,12 @@ dependencies:
|
|||
media_kit_video: 2.0.1
|
||||
media_kit_libs_video: 1.0.7
|
||||
measure_size: ^5.0.2
|
||||
material_segmented_list: ^1.0.5
|
||||
m3e_buttons: ^0.0.3
|
||||
navigation_rail_m3e:
|
||||
git:
|
||||
url: https://github.com/Henry-Hiles/material_3_expressive
|
||||
path: packages/navigation_rail_m3e
|
||||
m3e_card_list: ^0.1.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: 2.15.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue