🎨 Use feature based folder structure
This commit is contained in:
925
lib/realms/realm/realm_detail.dart
Normal file
925
lib/realms/realm/realm_detail.dart
Normal file
@@ -0,0 +1,925 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:island/accounts/accounts_widgets/account/account_pfc.dart';
|
||||
import 'package:island/accounts/accounts_widgets/account/account_picker.dart';
|
||||
import 'package:island/accounts/accounts_widgets/account/status.dart';
|
||||
import 'package:island/chat/chat_widgets/chat_room_list_tile.dart';
|
||||
import 'package:island/pagination/pagination.dart';
|
||||
import 'package:island/posts/post/post_list.dart';
|
||||
import 'package:island/posts/posts_widgets/post/post_item.dart';
|
||||
import 'package:island/posts/posts_widgets/post/post_list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:island/chat/chat_models/chat.dart';
|
||||
import 'package:island/core/services/color.dart';
|
||||
import 'package:island/core/services/responsive.dart';
|
||||
import 'package:island/core/services/color_extraction.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/realms/realm/realms.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/core/network.dart';
|
||||
import 'package:island/core/config.dart';
|
||||
import 'package:island/shared/widgets/alert.dart';
|
||||
import 'package:island/shared/widgets/app_scaffold.dart';
|
||||
import 'package:island/drive/drive_widgets/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:island/shared/widgets/pagination_list.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class _RealmPinnedPostsPageView extends HookConsumerWidget {
|
||||
final String realmSlug;
|
||||
|
||||
const _RealmPinnedPostsPageView({required this.realmSlug});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final provider = postListProvider(
|
||||
PostListQueryConfig(
|
||||
id: 'realm-$realmSlug-pinned',
|
||||
initialFilter: PostListQuery(realm: realmSlug, pinned: true),
|
||||
),
|
||||
);
|
||||
final pinnedPosts = ref.watch(provider);
|
||||
final pageController = usePageController();
|
||||
final currentPage = useState(0);
|
||||
|
||||
useEffect(() {
|
||||
void listener() {
|
||||
currentPage.value = pageController.page?.round() ?? 0;
|
||||
}
|
||||
|
||||
pageController.addListener(listener);
|
||||
return () => pageController.removeListener(listener);
|
||||
}, [pageController]);
|
||||
|
||||
return pinnedPosts.when(
|
||||
data: (data) {
|
||||
if (data.items.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
initiallyExpanded: true,
|
||||
leading: const Icon(Symbols.push_pin),
|
||||
title: Text('pinnedPosts'.tr()),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
collapsedShape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 400,
|
||||
child: Stack(
|
||||
children: [
|
||||
PageView.builder(
|
||||
controller: pageController,
|
||||
itemCount: data.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: SingleChildScrollView(
|
||||
child: Card(
|
||||
child: PostActionableItem(
|
||||
item: data.items[index],
|
||||
borderRadius: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(
|
||||
data.items.length,
|
||||
(index) => AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
margin: EdgeInsets.symmetric(horizontal: 4),
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: index == currentPage.value
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final realmAppbarForegroundColorProvider = FutureProvider.autoDispose
|
||||
.family<Color?, String>((ref, realmSlug) async {
|
||||
final realm = await ref.watch(realmProvider(realmSlug).future);
|
||||
if (realm?.background == null) return null;
|
||||
final colors = await ColorExtractionService.getColorsFromImage(
|
||||
CloudImageWidget.provider(
|
||||
file: realm!.background!,
|
||||
serverUrl: ref.watch(serverUrlProvider),
|
||||
),
|
||||
);
|
||||
if (colors.isEmpty) return null;
|
||||
final dominantColor = colors.first;
|
||||
return dominantColor.computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white;
|
||||
});
|
||||
|
||||
final realmIdentityProvider = FutureProvider.autoDispose
|
||||
.family<SnRealmMember?, String>((ref, realmSlug) async {
|
||||
try {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final response = await apiClient.get(
|
||||
'/pass/realms/$realmSlug/members/me',
|
||||
);
|
||||
return SnRealmMember.fromJson(response.data);
|
||||
} catch (err) {
|
||||
if (err is DioException && err.response?.statusCode == 404) {
|
||||
return null; // No identity found, user is not a member
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
});
|
||||
|
||||
final realmChatRoomsProvider = FutureProvider.autoDispose
|
||||
.family<List<SnChatRoom>, String>((ref, realmSlug) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final response = await apiClient.get('/messager/realms/$realmSlug/chat');
|
||||
return (response.data as List)
|
||||
.map((e) => SnChatRoom.fromJson(e))
|
||||
.toList();
|
||||
});
|
||||
|
||||
class RealmDetailScreen extends HookConsumerWidget {
|
||||
final String slug;
|
||||
|
||||
const RealmDetailScreen({super.key, required this.slug});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final realmState = ref.watch(realmProvider(slug));
|
||||
final appbarColor = ref.watch(realmAppbarForegroundColorProvider(slug));
|
||||
|
||||
final iconShadow = Shadow(
|
||||
color: appbarColor.value?.invert ?? Colors.black54,
|
||||
blurRadius: 5.0,
|
||||
offset: Offset(1.0, 1.0),
|
||||
);
|
||||
|
||||
final realmIdentity = ref.watch(realmIdentityProvider(slug));
|
||||
final realmChatRooms = ref.watch(realmChatRoomsProvider(slug));
|
||||
|
||||
Widget realmDescriptionWidget(SnRealm realm) => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
collapsedShape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
title: const Text('description').tr(),
|
||||
initiallyExpanded:
|
||||
realmIdentity.hasValue && realmIdentity.value == null,
|
||||
tilePadding: EdgeInsets.only(left: 24, right: 20),
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
realm.description,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
).padding(horizontal: 20, bottom: 16, top: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget realmActionWidget(SnRealm realm) => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
try {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post('/pass/realms/$slug/members/me');
|
||||
ref.invalidate(realmIdentityProvider(slug));
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
showSnackBar('realmJoinSuccess'.tr());
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Symbols.add),
|
||||
label: const Text('realmJoin').tr(),
|
||||
).padding(all: 16),
|
||||
);
|
||||
|
||||
Widget realmChatRoomListWidget(SnRealm realm) => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'chatTabGroup',
|
||||
).tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||
realmChatRooms.when(
|
||||
loading: () => Center(child: CircularProgressIndicator()),
|
||||
error: (error, _) => Center(child: Text('Error: $error')),
|
||||
data: (rooms) {
|
||||
if (rooms.isEmpty) {
|
||||
return Text(
|
||||
'dataEmpty',
|
||||
).tr().padding(horizontal: 24, bottom: 12);
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
for (final room in rooms)
|
||||
ChatRoomListTile(
|
||||
room: room,
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {'id': room.id},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: isWideScreen(context)
|
||||
? realmState.when(
|
||||
data: (realm) => AppBar(
|
||||
foregroundColor: appbarColor.value,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [iconShadow],
|
||||
),
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: realm!.background != null
|
||||
? CloudImageWidget(file: realm.background!)
|
||||
: Container(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
realm.name,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
shadows: [iconShadow],
|
||||
),
|
||||
),
|
||||
background: Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.people, shadows: [iconShadow]),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
_RealmMemberListSheet(realmSlug: slug),
|
||||
);
|
||||
},
|
||||
),
|
||||
_RealmActionMenu(realmSlug: slug, iconShadow: iconShadow),
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
error: (_, _) => AppBar(leading: PageBackButton()),
|
||||
loading: () => AppBar(leading: PageBackButton()),
|
||||
)
|
||||
: null,
|
||||
body: realmState.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, _) => Center(child: Text('Error: $error')),
|
||||
data: (realm) => isWideScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: _RealmPinnedPostsPageView(realmSlug: slug),
|
||||
),
|
||||
SliverPostList(
|
||||
query: PostListQuery(realm: slug, pinned: false),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
realmIdentity.when(
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
data: (identity) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
realmDescriptionWidget(realm!),
|
||||
if (identity == null && realm.isCommunity)
|
||||
realmActionWidget(realm)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
realmChatRoomListWidget(realm!),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 8, top: 8)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
expandedHeight: 180,
|
||||
pinned: true,
|
||||
foregroundColor: appbarColor.value,
|
||||
leading: PageBackButton(
|
||||
color: appbarColor.value,
|
||||
shadows: [iconShadow],
|
||||
),
|
||||
flexibleSpace: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: realm!.background != null
|
||||
? CloudImageWidget(file: realm.background!)
|
||||
: Container(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).appBarTheme.backgroundColor,
|
||||
),
|
||||
),
|
||||
FlexibleSpaceBar(
|
||||
title: Text(
|
||||
realm.name,
|
||||
style: TextStyle(
|
||||
color:
|
||||
appbarColor.value ??
|
||||
Theme.of(context).appBarTheme.foregroundColor,
|
||||
shadows: [iconShadow],
|
||||
),
|
||||
),
|
||||
background:
|
||||
Container(), // Empty container since background is handled by Stack
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.people, shadows: [iconShadow]),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
_RealmMemberListSheet(realmSlug: slug),
|
||||
);
|
||||
},
|
||||
),
|
||||
_RealmActionMenu(realmSlug: slug, iconShadow: iconShadow),
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
SliverGap(4),
|
||||
SliverToBoxAdapter(
|
||||
child: realmIdentity.when(
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
data: (identity) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
realmDescriptionWidget(realm),
|
||||
if (identity == null && realm.isCommunity)
|
||||
realmActionWidget(realm)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: realmChatRoomListWidget(realm)),
|
||||
SliverToBoxAdapter(
|
||||
child: _RealmPinnedPostsPageView(realmSlug: slug),
|
||||
),
|
||||
SliverPostList(
|
||||
query: PostListQuery(realm: slug, pinned: false),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RealmActionMenu extends HookConsumerWidget {
|
||||
final String realmSlug;
|
||||
final Shadow iconShadow;
|
||||
|
||||
const _RealmActionMenu({required this.realmSlug, required this.iconShadow});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final realmIdentity = ref.watch(realmIdentityProvider(realmSlug));
|
||||
final isModerator = realmIdentity.when(
|
||||
data: (identity) => (identity?.role ?? 0) >= 50,
|
||||
loading: () => false,
|
||||
error: (_, _) => false,
|
||||
);
|
||||
|
||||
return PopupMenuButton(
|
||||
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
|
||||
itemBuilder: (context) => [
|
||||
if (isModerator)
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
context.pushReplacementNamed(
|
||||
'realmEdit',
|
||||
pathParameters: {'slug': realmSlug},
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.edit,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
const Gap(12),
|
||||
const Text('editRealm').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
realmIdentity.when(
|
||||
data: (identity) => (identity?.role ?? 0) >= 100
|
||||
? PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.delete, color: Colors.red),
|
||||
const Gap(12),
|
||||
const Text(
|
||||
'deleteRealm',
|
||||
style: TextStyle(color: Colors.red),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showConfirmAlert(
|
||||
'deleteRealmHint'.tr(),
|
||||
'deleteRealm'.tr(),
|
||||
isDanger: true,
|
||||
).then((confirm) {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
client.delete('/pass/realms/$realmSlug');
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
if (context.mounted) {
|
||||
context.pop(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
: PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.exit_to_app,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const Gap(12),
|
||||
Text(
|
||||
'leaveRealm',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showConfirmAlert(
|
||||
'leaveRealmHint'.tr(),
|
||||
'leaveRealm'.tr(),
|
||||
).then((confirm) async {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.delete(
|
||||
'/pass/realms/$realmSlug/members/me',
|
||||
);
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
if (context.mounted) {
|
||||
context.pop(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
loading: () => const PopupMenuItem(
|
||||
enabled: false,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (_, _) => PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.exit_to_app,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const Gap(12),
|
||||
Text(
|
||||
'leaveRealm',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showConfirmAlert('leaveRealmHint'.tr(), 'leaveRealm'.tr()).then((
|
||||
confirm,
|
||||
) async {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.delete('/pass/realms/$realmSlug/members/me');
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
if (context.mounted) {
|
||||
context.pop(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final realmMemberListNotifierProvider = AsyncNotifierProvider.autoDispose
|
||||
.family(RealmMemberListNotifier.new);
|
||||
|
||||
class RealmMemberListNotifier
|
||||
extends AsyncNotifier<PaginationState<SnRealmMember>>
|
||||
with AsyncPaginationController<SnRealmMember> {
|
||||
String arg;
|
||||
RealmMemberListNotifier(this.arg);
|
||||
|
||||
static const int pageSize = 20;
|
||||
|
||||
@override
|
||||
Future<List<SnRealmMember>> fetch() async {
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
|
||||
final response = await apiClient.get(
|
||||
'/pass/realms/$arg/members',
|
||||
queryParameters: {
|
||||
'offset': fetchedCount,
|
||||
'take': pageSize,
|
||||
'withStatus': true,
|
||||
},
|
||||
);
|
||||
|
||||
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
||||
final List<dynamic> data = response.data;
|
||||
return data.map((e) => SnRealmMember.fromJson(e)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class _RealmMemberListSheet extends HookConsumerWidget {
|
||||
final String realmSlug;
|
||||
const _RealmMemberListSheet({required this.realmSlug});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final memberListProvider = realmMemberListNotifierProvider(realmSlug);
|
||||
|
||||
final memberListState = ref.watch(memberListProvider);
|
||||
final memberListNotifier = ref.watch(memberListProvider.notifier);
|
||||
final realmIdentity = ref.watch(realmIdentityProvider(realmSlug));
|
||||
|
||||
Future<void> invitePerson() async {
|
||||
final result = await showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (context) => const AccountPickerSheet(),
|
||||
);
|
||||
if (result == null) return;
|
||||
try {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.post(
|
||||
'/pass/realms/invites/$realmSlug',
|
||||
data: {'related_user_id': result.id, 'role': 0},
|
||||
);
|
||||
// Refresh the provider
|
||||
memberListNotifier.refresh();
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildMemberListHeader() {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Consumer(
|
||||
builder: (context, ref, _) {
|
||||
return Text(
|
||||
'members'.plural(memberListState.value?.totalCount ?? 0),
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.person_add),
|
||||
onPressed: invitePerson,
|
||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.refresh),
|
||||
onPressed: () {
|
||||
// Refresh the provider
|
||||
ref.invalidate(memberListProvider);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildMemberListContent() {
|
||||
return Expanded(
|
||||
child: PaginationList(
|
||||
provider: memberListProvider,
|
||||
notifier: memberListProvider.notifier,
|
||||
itemBuilder: (context, index, member) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
||||
leading: AccountPfcRegion(
|
||||
uname: member.account!.name,
|
||||
child: ProfilePictureWidget(
|
||||
file: member.account!.profile.picture,
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
member.account!.nick,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (member.status != null)
|
||||
AccountStatusLabel(status: member.status!),
|
||||
if (member.joinedAt == null)
|
||||
const Icon(Symbols.pending_actions, size: 20),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Text(
|
||||
member.role >= 100
|
||||
? 'permissionOwner'
|
||||
: member.role >= 50
|
||||
? 'permissionModerator'
|
||||
: 'permissionMember',
|
||||
).tr(),
|
||||
Text('·').bold().padding(horizontal: 6),
|
||||
Expanded(child: Text("@${member.account!.name}")),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if ((realmIdentity.value?.role ?? 0) >= 50)
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.edit),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => _RealmMemberRoleSheet(
|
||||
realmSlug: realmSlug,
|
||||
member: member,
|
||||
),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
// Refresh the provider
|
||||
ref.invalidate(memberListProvider);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
if ((realmIdentity.value?.role ?? 0) >= 50)
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.delete),
|
||||
onPressed: () {
|
||||
showConfirmAlert(
|
||||
'removeRealmMemberHint'.tr(),
|
||||
'removeRealmMember'.tr(),
|
||||
).then((confirm) async {
|
||||
if (confirm != true) return;
|
||||
try {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.delete(
|
||||
'/pass/realms/$realmSlug/members/${member.accountId}',
|
||||
);
|
||||
// Refresh the provider
|
||||
ref.invalidate(memberListProvider);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
buildMemberListHeader(),
|
||||
const Divider(height: 1),
|
||||
buildMemberListContent(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RealmMemberRoleSheet extends HookConsumerWidget {
|
||||
final String realmSlug;
|
||||
final SnRealmMember member;
|
||||
|
||||
const _RealmMemberRoleSheet({required this.realmSlug, required this.member});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final roleController = useTextEditingController(
|
||||
text: member.role.toString(),
|
||||
);
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 20,
|
||||
right: 16,
|
||||
bottom: 12,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'memberRoleEdit'.tr(args: [member.account!.name]),
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size(36, 36),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Autocomplete<int>(
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
if (textEditingValue.text.isEmpty) {
|
||||
return const [100, 50, 0];
|
||||
}
|
||||
final int? value = int.tryParse(textEditingValue.text);
|
||||
if (value == null) return const [100, 50, 0];
|
||||
return [100, 50, 0].where(
|
||||
(option) =>
|
||||
option.toString().contains(textEditingValue.text),
|
||||
);
|
||||
},
|
||||
onSelected: (int selection) {
|
||||
roleController.text = selection.toString();
|
||||
},
|
||||
fieldViewBuilder:
|
||||
(context, controller, focusNode, onFieldSubmitted) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'memberRole'.tr(),
|
||||
helperText: 'memberRoleHint'.tr(),
|
||||
),
|
||||
onTapOutside: (event) => focusNode.unfocus(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(16),
|
||||
FilledButton.icon(
|
||||
onPressed: () async {
|
||||
try {
|
||||
final newRole = int.parse(roleController.text);
|
||||
if (newRole < 0 || newRole > 100) {
|
||||
throw 'Role must be between 0 and 100';
|
||||
}
|
||||
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.patch(
|
||||
'/pass/realms/$realmSlug/members/${member.accountId}/role',
|
||||
data: newRole,
|
||||
);
|
||||
|
||||
if (context.mounted) Navigator.pop(context, true);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Symbols.save),
|
||||
label: const Text('saveChanges').tr(),
|
||||
),
|
||||
],
|
||||
).padding(vertical: 16, horizontal: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
271
lib/realms/realm/realm_form.dart
Normal file
271
lib/realms/realm/realm_form.dart
Normal file
@@ -0,0 +1,271 @@
|
||||
import 'package:croppy/croppy.dart' show CropAspectRatio;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/core/services/image.dart';
|
||||
import 'package:island/drive/drive_models/file.dart';
|
||||
import 'package:island/realms/realm/realms.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/core/network.dart';
|
||||
import 'package:island/drive/drive_service.dart';
|
||||
import 'package:island/shared/widgets/alert.dart';
|
||||
import 'package:island/shared/widgets/app_scaffold.dart';
|
||||
import 'package:island/drive/drive_widgets/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class NewRealmScreen extends StatelessWidget {
|
||||
const NewRealmScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const EditRealmScreen();
|
||||
}
|
||||
}
|
||||
|
||||
class EditRealmScreen extends HookConsumerWidget {
|
||||
final String? slug;
|
||||
const EditRealmScreen({super.key, this.slug});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final submitting = useState(false);
|
||||
|
||||
final picture = useState<SnCloudFile?>(null);
|
||||
final background = useState<SnCloudFile?>(null);
|
||||
final isPublic = useState(true);
|
||||
final isCommunity = useState(false);
|
||||
|
||||
final slugController = useTextEditingController();
|
||||
final nameController = useTextEditingController();
|
||||
final descriptionController = useTextEditingController();
|
||||
|
||||
final formKey = useMemoized(GlobalKey<FormState>.new, const []);
|
||||
|
||||
final realm = ref.watch(realmProvider(slug));
|
||||
|
||||
useEffect(() {
|
||||
if (realm.value != null) {
|
||||
picture.value = realm.value!.picture;
|
||||
background.value = realm.value!.background;
|
||||
slugController.text = realm.value!.slug;
|
||||
nameController.text = realm.value!.name;
|
||||
descriptionController.text = realm.value!.description;
|
||||
isPublic.value = realm.value!.isPublic;
|
||||
isCommunity.value = realm.value!.isCommunity;
|
||||
}
|
||||
return null;
|
||||
}, [realm]);
|
||||
|
||||
void setPicture(String position) async {
|
||||
showLoadingModal(context);
|
||||
var result = await ref
|
||||
.read(imagePickerProvider)
|
||||
.pickImage(source: ImageSource.gallery);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
hideLoadingModal(context);
|
||||
result = await cropImage(
|
||||
context,
|
||||
image: result,
|
||||
allowedAspectRatios: [
|
||||
if (position == 'background')
|
||||
const CropAspectRatio(height: 7, width: 16)
|
||||
else
|
||||
const CropAspectRatio(height: 1, width: 1),
|
||||
],
|
||||
);
|
||||
if (result == null) {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
showLoadingModal(context);
|
||||
submitting.value = true;
|
||||
try {
|
||||
final cloudFile = await FileUploader.createCloudFile(
|
||||
ref: ref,
|
||||
fileData: UniversalFile(data: result, type: UniversalFileType.image),
|
||||
).future;
|
||||
if (cloudFile == null) {
|
||||
throw ArgumentError('Failed to upload the file...');
|
||||
}
|
||||
switch (position) {
|
||||
case 'picture':
|
||||
picture.value = cloudFile;
|
||||
case 'background':
|
||||
background.value = cloudFile;
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
if (context.mounted) hideLoadingModal(context);
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> performAction() async {
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.request(
|
||||
'/pass${slug == null ? '/realms' : '/realms/$slug'}',
|
||||
data: {
|
||||
'slug': slugController.text,
|
||||
'name': nameController.text,
|
||||
'description': descriptionController.text,
|
||||
'background_id': background.value?.id,
|
||||
'picture_id': picture.value?.id,
|
||||
'is_public': isPublic.value,
|
||||
'is_community': isCommunity.value,
|
||||
},
|
||||
options: Options(method: slug == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.pop(SnRealm.fromJson(resp.data));
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()),
|
||||
leading: const PageBackButton(),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: background.value != null
|
||||
? CloudFileWidget(
|
||||
item: background.value!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('background');
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
left: 20,
|
||||
bottom: -32,
|
||||
child: GestureDetector(
|
||||
child: ProfilePictureWidget(
|
||||
file: picture.value,
|
||||
radius: 40,
|
||||
fallbackIcon: Symbols.group,
|
||||
),
|
||||
onTap: () {
|
||||
setPicture('picture');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(bottom: 32),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: slugController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'slug'.tr(),
|
||||
helperText: 'slugHint'.tr(),
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(labelText: 'name'.tr()),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: descriptionController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'description'.tr(),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.public),
|
||||
title: Text('publicRealm').tr(),
|
||||
subtitle: Text('publicRealmDescription').tr(),
|
||||
value: isPublic.value,
|
||||
onChanged: (value) {
|
||||
isPublic.value = value ?? true;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
secondary: const Icon(Symbols.travel_explore),
|
||||
title: Text('communityRealm').tr(),
|
||||
subtitle: Text('communityRealmDescription').tr(),
|
||||
value: isCommunity.value,
|
||||
onChanged: (value) {
|
||||
isCommunity.value = value ?? false;
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
onPressed: submitting.value ? null : performAction,
|
||||
label: Text('saveChanges'.tr()),
|
||||
icon: const Icon(Symbols.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(all: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
254
lib/realms/realm/realms.dart
Normal file
254
lib/realms/realm/realms.dart
Normal file
@@ -0,0 +1,254 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/core/network.dart';
|
||||
import 'package:island/accounts/accounts_pod.dart';
|
||||
import 'package:island/realms/realms_widgets/realm/realm_list_tile.dart';
|
||||
import 'package:island/shared/widgets/alert.dart';
|
||||
import 'package:island/shared/widgets/app_scaffold.dart';
|
||||
import 'package:island/drive/drive_widgets/cloud_files.dart';
|
||||
import 'package:island/core/widgets/content/sheet.dart';
|
||||
import 'package:island/shared/widgets/response.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:island/shared/widgets/extended_refresh_indicator.dart';
|
||||
|
||||
part 'realms.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<SnRealm>> realmsJoined(Ref ref) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/pass/realms');
|
||||
return resp.data.map((e) => SnRealm.fromJson(e)).cast<SnRealm>().toList();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnRealm?> realm(Ref ref, String? identifier) async {
|
||||
if (identifier == null) return null;
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/pass/realms/$identifier');
|
||||
return SnRealm.fromJson(resp.data);
|
||||
}
|
||||
|
||||
class RealmListScreen extends HookConsumerWidget {
|
||||
const RealmListScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final realms = ref.watch(realmsJoinedProvider);
|
||||
final realmInvites = ref.watch(realmInvitesProvider);
|
||||
|
||||
final userInfo = ref.watch(userInfoProvider);
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(
|
||||
title: const Text('realms').tr(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.travel_explore),
|
||||
onPressed: () => context.pushNamed(
|
||||
'universalSearch',
|
||||
queryParameters: {'tab': 'realms'},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Badge(
|
||||
label: Text(
|
||||
realmInvites.when(
|
||||
data: (invites) => invites.length.toString(),
|
||||
error: (_, _) => '0',
|
||||
loading: () => '0',
|
||||
),
|
||||
),
|
||||
isLabelVisible: realmInvites.when(
|
||||
data: (invites) => invites.isNotEmpty,
|
||||
error: (_, _) => false,
|
||||
loading: () => false,
|
||||
),
|
||||
child: const Icon(Symbols.email),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (_) => const _RealmInviteSheet(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
floatingActionButton: userInfo.value != null
|
||||
? FloatingActionButton(
|
||||
child: const Icon(Symbols.add),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Gap(40),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
leading: const Icon(Symbols.group_add),
|
||||
title: Text('createRealm').tr(),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
context.pushNamed('realmNew').then((value) {
|
||||
if (value != null) {
|
||||
// Fire realm refresh event if needed
|
||||
// eventBus.fire(const RealmsRefreshEvent());
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
const Gap(16),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
).padding(bottom: MediaQuery.of(context).padding.bottom)
|
||||
: null,
|
||||
body: userInfo.value == null
|
||||
? const ResponseUnauthorizedWidget()
|
||||
: ExtendedRefreshIndicator(
|
||||
child: realms.when(
|
||||
data: (value) => Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.only(
|
||||
top: 8,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 8,
|
||||
),
|
||||
itemCount: value.length,
|
||||
itemBuilder: (context, item) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 540),
|
||||
child: RealmListTile(realm: value[item]),
|
||||
).padding(horizontal: 8).center();
|
||||
},
|
||||
separatorBuilder: (_, _) => const Gap(8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => ResponseErrorWidget(
|
||||
error: e,
|
||||
onRetry: () => ref.invalidate(realmsJoinedProvider),
|
||||
),
|
||||
),
|
||||
onRefresh: () => ref.refresh(realmsJoinedProvider.future),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnRealmMember>> realmInvites(Ref ref) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/pass/realms/invites');
|
||||
return resp.data
|
||||
.map((e) => SnRealmMember.fromJson(e))
|
||||
.cast<SnRealmMember>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
class _RealmInviteSheet extends HookConsumerWidget {
|
||||
const _RealmInviteSheet();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final invites = ref.watch(realmInvitesProvider);
|
||||
|
||||
Future<void> acceptInvite(SnRealmMember invite) async {
|
||||
try {
|
||||
final client = ref.read(apiClientProvider);
|
||||
await client.post('/pass/realms/invites/${invite.realm!.slug}/accept');
|
||||
ref.invalidate(realmInvitesProvider);
|
||||
ref.invalidate(realmsJoinedProvider);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> declineInvite(SnRealmMember invite) async {
|
||||
try {
|
||||
final client = ref.read(apiClientProvider);
|
||||
await client.post('/pass/realms/invites/${invite.realm!.slug}/decline');
|
||||
ref.invalidate(realmInvitesProvider);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
}
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: 'invites'.tr(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.refresh),
|
||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||
onPressed: () {
|
||||
ref.invalidate(realmInvitesProvider);
|
||||
},
|
||||
),
|
||||
],
|
||||
child: invites.when(
|
||||
data: (items) => items.isEmpty
|
||||
? Center(
|
||||
child: Text('invitesEmpty', textAlign: TextAlign.center).tr(),
|
||||
)
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final invite = items[index];
|
||||
return ListTile(
|
||||
leading: ProfilePictureWidget(
|
||||
file: invite.realm!.picture,
|
||||
fallbackIcon: Symbols.group,
|
||||
),
|
||||
title: Text(invite.realm!.name),
|
||||
subtitle: Text(
|
||||
invite.role >= 100
|
||||
? 'permissionOwner'
|
||||
: invite.role >= 50
|
||||
? 'permissionModerator'
|
||||
: 'permissionMember',
|
||||
).tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.check),
|
||||
onPressed: () => acceptInvite(invite),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.close),
|
||||
onPressed: () => declineInvite(invite),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(realmInvitesProvider),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
160
lib/realms/realm/realms.g.dart
Normal file
160
lib/realms/realm/realms.g.dart
Normal file
@@ -0,0 +1,160 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'realms.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(realmsJoined)
|
||||
final realmsJoinedProvider = RealmsJoinedProvider._();
|
||||
|
||||
final class RealmsJoinedProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<SnRealm>>,
|
||||
List<SnRealm>,
|
||||
FutureOr<List<SnRealm>>
|
||||
>
|
||||
with $FutureModifier<List<SnRealm>>, $FutureProvider<List<SnRealm>> {
|
||||
RealmsJoinedProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'realmsJoinedProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$realmsJoinedHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<SnRealm>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<SnRealm>> create(Ref ref) {
|
||||
return realmsJoined(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$realmsJoinedHash() => r'b15029acd38f03bbbb8708adb78f25ac357a0421';
|
||||
|
||||
@ProviderFor(realm)
|
||||
final realmProvider = RealmFamily._();
|
||||
|
||||
final class RealmProvider
|
||||
extends
|
||||
$FunctionalProvider<AsyncValue<SnRealm?>, SnRealm?, FutureOr<SnRealm?>>
|
||||
with $FutureModifier<SnRealm?>, $FutureProvider<SnRealm?> {
|
||||
RealmProvider._({
|
||||
required RealmFamily super.from,
|
||||
required String? super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'realmProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$realmHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'realmProvider'
|
||||
''
|
||||
'($argument)';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<SnRealm?> $createElement($ProviderPointer pointer) =>
|
||||
$FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<SnRealm?> create(Ref ref) {
|
||||
final argument = this.argument as String?;
|
||||
return realm(ref, argument);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is RealmProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$realmHash() => r'71a126ab2810566646e1629290c1ce9ffa0839e3';
|
||||
|
||||
final class RealmFamily extends $Family
|
||||
with $FunctionalFamilyOverride<FutureOr<SnRealm?>, String?> {
|
||||
RealmFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'realmProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
RealmProvider call(String? identifier) =>
|
||||
RealmProvider._(argument: identifier, from: this);
|
||||
|
||||
@override
|
||||
String toString() => r'realmProvider';
|
||||
}
|
||||
|
||||
@ProviderFor(realmInvites)
|
||||
final realmInvitesProvider = RealmInvitesProvider._();
|
||||
|
||||
final class RealmInvitesProvider
|
||||
extends
|
||||
$FunctionalProvider<
|
||||
AsyncValue<List<SnRealmMember>>,
|
||||
List<SnRealmMember>,
|
||||
FutureOr<List<SnRealmMember>>
|
||||
>
|
||||
with
|
||||
$FutureModifier<List<SnRealmMember>>,
|
||||
$FutureProvider<List<SnRealmMember>> {
|
||||
RealmInvitesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'realmInvitesProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$realmInvitesHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<List<SnRealmMember>> $createElement(
|
||||
$ProviderPointer pointer,
|
||||
) => $FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<List<SnRealmMember>> create(Ref ref) {
|
||||
return realmInvites(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$realmInvitesHash() => r'92cce0978c7ca8813e27ae42fc6f3a93a09a8962';
|
||||
48
lib/realms/realms_models/realm.dart
Normal file
48
lib/realms/realms_models/realm.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/drive/drive_models/file.dart';
|
||||
import 'package:island/accounts/accounts_models/account.dart';
|
||||
|
||||
part 'realm.freezed.dart';
|
||||
part 'realm.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class SnRealm with _$SnRealm {
|
||||
const factory SnRealm({
|
||||
required String id,
|
||||
required String slug,
|
||||
@Default('') String name,
|
||||
@Default('') String description,
|
||||
required String? verifiedAs,
|
||||
required DateTime? verifiedAt,
|
||||
required bool isCommunity,
|
||||
required bool isPublic,
|
||||
required SnCloudFile? picture,
|
||||
required SnCloudFile? background,
|
||||
required String accountId,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
}) = _SnRealm;
|
||||
|
||||
factory SnRealm.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnRealmFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnRealmMember with _$SnRealmMember {
|
||||
const factory SnRealmMember({
|
||||
required String realmId,
|
||||
required SnRealm? realm,
|
||||
required String accountId,
|
||||
required SnAccount? account,
|
||||
required int role,
|
||||
required DateTime? joinedAt,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
required SnAccountStatus? status,
|
||||
}) = _SnRealmMember;
|
||||
|
||||
factory SnRealmMember.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnRealmMemberFromJson(json);
|
||||
}
|
||||
714
lib/realms/realms_models/realm.freezed.dart
Normal file
714
lib/realms/realms_models/realm.freezed.dart
Normal file
@@ -0,0 +1,714 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'realm.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnRealm {
|
||||
|
||||
String get id; String get slug; String get name; String get description; String? get verifiedAs; DateTime? get verifiedAt; bool get isCommunity; bool get isPublic; SnCloudFile? get picture; SnCloudFile? get background; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnRealmCopyWith<SnRealm> get copyWith => _$SnRealmCopyWithImpl<SnRealm>(this as SnRealm, _$identity);
|
||||
|
||||
/// Serializes this SnRealm to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,picture,background,accountId,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, picture: $picture, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnRealmCopyWith<$Res> {
|
||||
factory $SnRealmCopyWith(SnRealm value, $Res Function(SnRealm) _then) = _$SnRealmCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnRealmCopyWithImpl<$Res>
|
||||
implements $SnRealmCopyWith<$Res> {
|
||||
_$SnRealmCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnRealm _self;
|
||||
final $Res Function(SnRealm) _then;
|
||||
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,verifiedAs: freezed == verifiedAs ? _self.verifiedAs : verifiedAs // ignore: cast_nullable_to_non_nullable
|
||||
as String?,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
|
||||
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||
as SnCloudFile?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnCloudFileCopyWith<$Res>? get picture {
|
||||
if (_self.picture == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||
return _then(_self.copyWith(picture: value));
|
||||
});
|
||||
}/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnCloudFileCopyWith<$Res>? get background {
|
||||
if (_self.background == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||
return _then(_self.copyWith(background: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnRealm].
|
||||
extension SnRealmPatterns on SnRealm {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnRealm value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealm() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnRealm value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealm():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnRealm value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealm() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealm() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.description,_that.verifiedAs,_that.verifiedAt,_that.isCommunity,_that.isPublic,_that.picture,_that.background,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealm():
|
||||
return $default(_that.id,_that.slug,_that.name,_that.description,_that.verifiedAs,_that.verifiedAt,_that.isCommunity,_that.isPublic,_that.picture,_that.background,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealm() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.description,_that.verifiedAs,_that.verifiedAt,_that.isCommunity,_that.isPublic,_that.picture,_that.background,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnRealm implements SnRealm {
|
||||
const _SnRealm({required this.id, required this.slug, this.name = '', this.description = '', required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.picture, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||
factory _SnRealm.fromJson(Map<String, dynamic> json) => _$SnRealmFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String slug;
|
||||
@override@JsonKey() final String name;
|
||||
@override@JsonKey() final String description;
|
||||
@override final String? verifiedAs;
|
||||
@override final DateTime? verifiedAt;
|
||||
@override final bool isCommunity;
|
||||
@override final bool isPublic;
|
||||
@override final SnCloudFile? picture;
|
||||
@override final SnCloudFile? background;
|
||||
@override final String accountId;
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnRealmCopyWith<_SnRealm> get copyWith => __$SnRealmCopyWithImpl<_SnRealm>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnRealmToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealm&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.verifiedAs, verifiedAs) || other.verifiedAs == verifiedAs)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,slug,name,description,verifiedAs,verifiedAt,isCommunity,isPublic,picture,background,accountId,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnRealm(id: $id, slug: $slug, name: $name, description: $description, verifiedAs: $verifiedAs, verifiedAt: $verifiedAt, isCommunity: $isCommunity, isPublic: $isPublic, picture: $picture, background: $background, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnRealmCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
|
||||
factory _$SnRealmCopyWith(_SnRealm value, $Res Function(_SnRealm) _then) = __$SnRealmCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String slug, String name, String description, String? verifiedAs, DateTime? verifiedAt, bool isCommunity, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnRealmCopyWithImpl<$Res>
|
||||
implements _$SnRealmCopyWith<$Res> {
|
||||
__$SnRealmCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnRealm _self;
|
||||
final $Res Function(_SnRealm) _then;
|
||||
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = null,Object? verifiedAs = freezed,Object? verifiedAt = freezed,Object? isCommunity = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnRealm(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,verifiedAs: freezed == verifiedAs ? _self.verifiedAs : verifiedAs // ignore: cast_nullable_to_non_nullable
|
||||
as String?,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
|
||||
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||
as SnCloudFile?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnCloudFileCopyWith<$Res>? get picture {
|
||||
if (_self.picture == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||
return _then(_self.copyWith(picture: value));
|
||||
});
|
||||
}/// Create a copy of SnRealm
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnCloudFileCopyWith<$Res>? get background {
|
||||
if (_self.background == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||
return _then(_self.copyWith(background: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnRealmMember {
|
||||
|
||||
String get realmId; SnRealm? get realm; String get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; SnAccountStatus? get status;
|
||||
/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnRealmMemberCopyWith<SnRealmMember> get copyWith => _$SnRealmMemberCopyWithImpl<SnRealmMember>(this as SnRealmMember, _$identity);
|
||||
|
||||
/// Serializes this SnRealmMember to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.status, status) || other.status == status));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt,status);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, status: $status)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnRealmMemberCopyWith<$Res> {
|
||||
factory $SnRealmMemberCopyWith(SnRealmMember value, $Res Function(SnRealmMember) _then) = _$SnRealmMemberCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status
|
||||
});
|
||||
|
||||
|
||||
$SnRealmCopyWith<$Res>? get realm;$SnAccountCopyWith<$Res>? get account;$SnAccountStatusCopyWith<$Res>? get status;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnRealmMemberCopyWithImpl<$Res>
|
||||
implements $SnRealmMemberCopyWith<$Res> {
|
||||
_$SnRealmMemberCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnRealmMember _self;
|
||||
final $Res Function(SnRealmMember) _then;
|
||||
|
||||
/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? status = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||
as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
||||
as SnRealm?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
|
||||
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccountStatus?,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnRealmCopyWith<$Res>? get realm {
|
||||
if (_self.realm == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
|
||||
return _then(_self.copyWith(realm: value));
|
||||
});
|
||||
}/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get account {
|
||||
if (_self.account == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||
return _then(_self.copyWith(account: value));
|
||||
});
|
||||
}/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountStatusCopyWith<$Res>? get status {
|
||||
if (_self.status == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
|
||||
return _then(_self.copyWith(status: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnRealmMember].
|
||||
extension SnRealmMemberPatterns on SnRealmMember {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnRealmMember value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealmMember() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnRealmMember value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealmMember():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnRealmMember value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealmMember() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealmMember() when $default != null:
|
||||
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealmMember():
|
||||
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnRealmMember() when $default != null:
|
||||
return $default(_that.realmId,_that.realm,_that.accountId,_that.account,_that.role,_that.joinedAt,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.status);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnRealmMember implements SnRealmMember {
|
||||
const _SnRealmMember({required this.realmId, required this.realm, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt, required this.status});
|
||||
factory _SnRealmMember.fromJson(Map<String, dynamic> json) => _$SnRealmMemberFromJson(json);
|
||||
|
||||
@override final String realmId;
|
||||
@override final SnRealm? realm;
|
||||
@override final String accountId;
|
||||
@override final SnAccount? account;
|
||||
@override final int role;
|
||||
@override final DateTime? joinedAt;
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
@override final SnAccountStatus? status;
|
||||
|
||||
/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnRealmMemberCopyWith<_SnRealmMember> get copyWith => __$SnRealmMemberCopyWithImpl<_SnRealmMember>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnRealmMemberToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnRealmMember&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.status, status) || other.status == status));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,realmId,realm,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt,status);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnRealmMember(realmId: $realmId, realm: $realm, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, status: $status)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnRealmMemberCopyWith<$Res> implements $SnRealmMemberCopyWith<$Res> {
|
||||
factory _$SnRealmMemberCopyWith(_SnRealmMember value, $Res Function(_SnRealmMember) _then) = __$SnRealmMemberCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String realmId, SnRealm? realm, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, SnAccountStatus? status
|
||||
});
|
||||
|
||||
|
||||
@override $SnRealmCopyWith<$Res>? get realm;@override $SnAccountCopyWith<$Res>? get account;@override $SnAccountStatusCopyWith<$Res>? get status;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnRealmMemberCopyWithImpl<$Res>
|
||||
implements _$SnRealmMemberCopyWith<$Res> {
|
||||
__$SnRealmMemberCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnRealmMember _self;
|
||||
final $Res Function(_SnRealmMember) _then;
|
||||
|
||||
/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? realmId = null,Object? realm = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? status = freezed,}) {
|
||||
return _then(_SnRealmMember(
|
||||
realmId: null == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||
as String,realm: freezed == realm ? _self.realm : realm // ignore: cast_nullable_to_non_nullable
|
||||
as SnRealm?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
|
||||
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccountStatus?,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnRealmCopyWith<$Res>? get realm {
|
||||
if (_self.realm == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnRealmCopyWith<$Res>(_self.realm!, (value) {
|
||||
return _then(_self.copyWith(realm: value));
|
||||
});
|
||||
}/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountCopyWith<$Res>? get account {
|
||||
if (_self.account == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||
return _then(_self.copyWith(account: value));
|
||||
});
|
||||
}/// Create a copy of SnRealmMember
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnAccountStatusCopyWith<$Res>? get status {
|
||||
if (_self.status == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
|
||||
return _then(_self.copyWith(status: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
87
lib/realms/realms_models/realm.g.dart
Normal file
87
lib/realms/realms_models/realm.g.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'realm.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_SnRealm _$SnRealmFromJson(Map<String, dynamic> json) => _SnRealm(
|
||||
id: json['id'] as String,
|
||||
slug: json['slug'] as String,
|
||||
name: json['name'] as String? ?? '',
|
||||
description: json['description'] as String? ?? '',
|
||||
verifiedAs: json['verified_as'] as String?,
|
||||
verifiedAt: json['verified_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['verified_at'] as String),
|
||||
isCommunity: json['is_community'] as bool,
|
||||
isPublic: json['is_public'] as bool,
|
||||
picture: json['picture'] == null
|
||||
? null
|
||||
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
|
||||
background: json['background'] == null
|
||||
? null
|
||||
: SnCloudFile.fromJson(json['background'] as Map<String, dynamic>),
|
||||
accountId: json['account_id'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt: json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnRealmToJson(_SnRealm instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'slug': instance.slug,
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'verified_as': instance.verifiedAs,
|
||||
'verified_at': instance.verifiedAt?.toIso8601String(),
|
||||
'is_community': instance.isCommunity,
|
||||
'is_public': instance.isPublic,
|
||||
'picture': instance.picture?.toJson(),
|
||||
'background': instance.background?.toJson(),
|
||||
'account_id': instance.accountId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnRealmMember _$SnRealmMemberFromJson(Map<String, dynamic> json) =>
|
||||
_SnRealmMember(
|
||||
realmId: json['realm_id'] as String,
|
||||
realm: json['realm'] == null
|
||||
? null
|
||||
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
|
||||
accountId: json['account_id'] as String,
|
||||
account: json['account'] == null
|
||||
? null
|
||||
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
||||
role: (json['role'] as num).toInt(),
|
||||
joinedAt: json['joined_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['joined_at'] as String),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt: json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
status: json['status'] == null
|
||||
? null
|
||||
: SnAccountStatus.fromJson(json['status'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnRealmMemberToJson(_SnRealmMember instance) =>
|
||||
<String, dynamic>{
|
||||
'realm_id': instance.realmId,
|
||||
'realm': instance.realm?.toJson(),
|
||||
'account_id': instance.accountId,
|
||||
'account': instance.account?.toJson(),
|
||||
'role': instance.role,
|
||||
'joined_at': instance.joinedAt?.toIso8601String(),
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'status': instance.status?.toJson(),
|
||||
};
|
||||
106
lib/realms/realms_widgets/realm/realm_card.dart
Normal file
106
lib/realms/realms_widgets/realm/realm_card.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/drive/drive_widgets/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class RealmDiscoveryCard extends ConsumerWidget {
|
||||
final SnRealm realm;
|
||||
final double? maxWidth;
|
||||
|
||||
const RealmDiscoveryCard({super.key, required this.realm, this.maxWidth});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget imageWidget;
|
||||
if (realm.picture != null) {
|
||||
imageWidget = imageWidget = CloudImageWidget(
|
||||
file: realm.background,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
} else {
|
||||
imageWidget = ColoredBox(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
);
|
||||
}
|
||||
|
||||
Widget card = Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'slug': realm.slug},
|
||||
);
|
||||
},
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
imageWidget,
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
Colors.black.withOpacity(0.7),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ProfilePictureWidget(
|
||||
file: realm.picture,
|
||||
fallbackIcon: Symbols.group,
|
||||
radius: 12,
|
||||
),
|
||||
),
|
||||
const Gap(2),
|
||||
Text(
|
||||
realm.name,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||
child: card,
|
||||
);
|
||||
}
|
||||
}
|
||||
78
lib/realms/realms_widgets/realm/realm_list.dart
Normal file
78
lib/realms/realms_widgets/realm/realm_list.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pagination/pagination.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/core/network.dart';
|
||||
import 'package:island/realms/realms_widgets/realm/realm_list_tile.dart';
|
||||
import 'package:island/shared/widgets/pagination_list.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
final realmListNotifierProvider = AsyncNotifierProvider.autoDispose.family(
|
||||
RealmListNotifier.new,
|
||||
);
|
||||
|
||||
class RealmListNotifier extends AsyncNotifier<PaginationState<SnRealm>>
|
||||
with AsyncPaginationController<SnRealm> {
|
||||
String? arg;
|
||||
RealmListNotifier(this.arg);
|
||||
|
||||
static const int _pageSize = 20;
|
||||
|
||||
@override
|
||||
FutureOr<PaginationState<SnRealm>> build() async {
|
||||
final items = await fetch();
|
||||
return PaginationState(
|
||||
items: items,
|
||||
isLoading: false,
|
||||
isReloading: false,
|
||||
totalCount: totalCount,
|
||||
hasMore: hasMore,
|
||||
cursor: cursor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SnRealm>> fetch() async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
|
||||
final queryParams = {
|
||||
'offset': fetchedCount,
|
||||
'take': _pageSize,
|
||||
if (arg != null && arg!.isNotEmpty) 'query': arg,
|
||||
};
|
||||
|
||||
final response = await client.get(
|
||||
'/pass/realms/public',
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
|
||||
final List<dynamic> data = response.data;
|
||||
return data.map((json) => SnRealm.fromJson(json)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class SliverRealmList extends HookConsumerWidget {
|
||||
const SliverRealmList({super.key, this.query});
|
||||
|
||||
final String? query;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final provider = realmListNotifierProvider(query);
|
||||
return PaginationList(
|
||||
provider: provider,
|
||||
notifier: provider.notifier,
|
||||
isSliver: true,
|
||||
isRefreshable: false,
|
||||
spacing: 8,
|
||||
itemBuilder: (context, index, realm) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 540),
|
||||
child: RealmListTile(realm: realm).padding(horizontal: 8),
|
||||
).center();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
75
lib/realms/realms_widgets/realm/realm_list_tile.dart
Normal file
75
lib/realms/realms_widgets/realm/realm_list_tile.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/drive/drive_widgets/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class RealmListTile extends StatelessWidget {
|
||||
const RealmListTile({super.key, required this.realm});
|
||||
|
||||
final SnRealm realm;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: realm.background == null
|
||||
? const SizedBox.shrink()
|
||||
: CloudImageWidget(file: realm.background),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -30,
|
||||
left: 18,
|
||||
child: ProfilePictureWidget(
|
||||
file: realm.picture,
|
||||
fallbackIcon: Symbols.group,
|
||||
radius: 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(20 + 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
realm.name,
|
||||
).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||
Text(
|
||||
realm.description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
).padding(horizontal: 24, bottom: 14),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
context.pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'slug': realm.slug},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/drive/drive_widgets/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class RealmSelectionDropdown extends StatelessWidget {
|
||||
final SnRealm? value;
|
||||
final List<SnRealm> realms;
|
||||
final ValueChanged<SnRealm?> onChanged;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const RealmSelectionDropdown({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.realms,
|
||||
required this.onChanged,
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<SnRealm?>(
|
||||
isExpanded: true,
|
||||
hint: Text('realmSelection').tr(),
|
||||
value: value,
|
||||
items: [
|
||||
DropdownMenuItem<SnRealm?>(
|
||||
value: null,
|
||||
child: Row(
|
||||
children: [
|
||||
const CircleAvatar(
|
||||
radius: 16,
|
||||
child: Icon(Symbols.person, fill: 1),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text('individual').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!isLoading && error == null)
|
||||
...realms.map(
|
||||
(realm) => DropdownMenuItem<SnRealm?>(
|
||||
value: realm,
|
||||
child: Row(
|
||||
children: [
|
||||
ProfilePictureWidget(
|
||||
file: realm.picture,
|
||||
fallbackIcon: Symbols.workspaces,
|
||||
radius: 16,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(realm.name),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: onChanged,
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.only(left: 4, right: 16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/realms/realms_widgets/realm/realm_tile.dart
Normal file
20
lib/realms/realms_widgets/realm/realm_tile.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/realms/realms_models/realm.dart';
|
||||
import 'package:island/drive/drive_widgets/cloud_files.dart';
|
||||
|
||||
class RealmTile extends HookConsumerWidget {
|
||||
final SnRealm realm;
|
||||
const RealmTile({super.key, required this.realm});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ListTile(
|
||||
leading: ProfilePictureWidget(file: realm.picture),
|
||||
title: Text(realm.name),
|
||||
subtitle: Text(realm.description),
|
||||
onTap: () => context.pushNamed('realmDetail', pathParameters: {'slug': realm.slug}),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user