🎨 Use feature based folder structure

This commit is contained in:
2026-02-06 00:37:02 +08:00
parent 62a3ea26e3
commit 862e3b451b
539 changed files with 8406 additions and 5056 deletions

View 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),
],
),
),
);
}
}

View 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),
),
],
),
),
);
}
}

View 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),
),
),
);
}
}

View 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';

View 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);
}

View 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

View 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(),
};

View 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,
);
}
}

View 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();
},
);
}
}

View 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},
);
},
),
);
}
}

View File

@@ -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),
),
),
);
}
}

View 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}),
);
}
}