Compare commits

...

4 Commits

Author SHA1 Message Date
835203706d Channel creation & alter 2024-11-16 16:55:31 +08:00
0e208cc320 Realm manage (CRUD) 2024-11-16 13:54:36 +08:00
ee2cb0c989 💫 Optimize nav transition 2024-11-15 23:08:29 +08:00
37c61a0406 Optimize nav transition performance 2024-11-15 22:46:12 +08:00
21 changed files with 2746 additions and 105 deletions

View File

@@ -17,6 +17,11 @@
"screenSettings": "Settings",
"screenAlbum": "Album",
"screenChat": "Chat",
"screenChatManage": "Edit Channel",
"screenChatNew": "New Channel",
"screenRealm": "Realm",
"screenRealmManage": "Edit Realm",
"screenRealmNew": "New Realm",
"dialogOkay": "Okay",
"dialogCancel": "Cancel",
"dialogConfirm": "Confirm",
@@ -32,6 +37,7 @@
"next": "Next",
"edit": "Edit",
"apply": "Apply",
"cancel": "Cancel",
"create": "Create",
"preview": "Preview",
"loading": "Loading...",
@@ -133,5 +139,23 @@
"sensitiveContentDescription": "This content has been marked as sensitive, and may not be suitable for all viewers.",
"sensitiveContentReveal": "Reveal",
"serverConnecting": "Connecting to server...",
"serverDisconnected": "Lost connection from server"
"serverDisconnected": "Lost connection from server",
"fieldChatAlias": "Channel Alias",
"fieldChatAliasHint": "The unique channel alias within the site, used to represent the channel in URL, leave blank to auto generate. Should be URL-Safe.",
"fieldChatName": "Name",
"fieldChatDescription": "Description",
"fieldChatBelongToRealm": "Belongs to",
"fieldChatBelongToRealmUnset": "Unset Channel Belongs to Realm",
"channelEditingNotice": "You are editing channel {}",
"channelDeleted": "Chat channel {} has been deleted." ,
"channelDelete": "Delete channel {}",
"channelDeleteDescription": "Are you sure you want to delete this channel? This operation is irreversible, all messages in this channel will be permanently deleted.",
"fieldRealmAlias": "Realm Alias",
"fieldRealmAliasHint": "The unique realm alias within the site, used to represent the realm in URL, leave blank to auto generate. Should be URL-Safe.",
"fieldRealmName": "Name",
"fieldRealmDescription": "Description",
"realmEditingNotice": "You are editing realm {}",
"realmDeleted": "Realm {} has been deleted.",
"realmDelete": "Delete realm {}",
"realmDeleteDescription": "Are you sure you want to delete this realm? This operation is irreversible, all resources (posts, chat channels, publishers, etc) belonging to this realm will be permanently deleted. Be careful and think twice!"
}

View File

@@ -17,6 +17,11 @@
"screenSettings": "设置",
"screenAlbum": "相册",
"screenChat": "聊天",
"screenChatManage": "编辑聊天频道",
"screenChatNew": "新建聊天频道",
"screenRealm": "领域",
"screenRealmManage": "编辑领域",
"screenRealmNew": "新建领域",
"dialogOkay": "好的",
"dialogCancel": "取消",
"dialogConfirm": "确认",
@@ -33,6 +38,7 @@
"next": "下一步",
"edit": "编辑",
"apply": "应用",
"cancel": "取消",
"create": "创建",
"preview": "预览",
"delete": "删除",
@@ -133,5 +139,23 @@
"sensitiveContentDescription": "此内容已被标记,可能不适合所有人查看。",
"sensitiveContentReveal": "显示内容",
"serverConnecting": "正在连接服务器…",
"serverDisconnected": "已与服务器断开连接"
"serverDisconnected": "已与服务器断开连接",
"fieldChatAlias": "频道别名",
"fieldChatAliasHint": "全站范围内唯一的频道别名,用于在 URL 中表示该频道,留空则自动生成。应遵循 URL-Safe 的原则。",
"fieldChatName": "名称",
"fieldChatDescription": "描述",
"fieldChatBelongToRealm": "所属领域",
"fieldChatBelongToRealmUnset": "未设置频道所属领域",
"channelEditingNotice": "您正在编辑频道 {}",
"channelDeleted": "聊天频道 {} 已被删除" ,
"channelDelete": "删除聊天频道 {}",
"channelDeleteDescription": "你确定要删除这个聊天频道吗?该操作不可撤销,其频道内的所有消息将被永久删除。",
"fieldRealmAlias": "领域别名",
"fieldRealmAliasHint": "全站范围内唯一的领域别名,用于在 URL 中表示该领域,留空则自动生成。应遵循 URL-Safe 的原则。",
"fieldRealmName": "名称",
"fieldRealmDescription": "描述",
"realmEditingNotice": "您正在编辑领域 {}",
"realmDeleted": "领域 {} 已被删除" ,
"realmDelete": "删除领域 {}",
"realmDeleteDescription": "你确定要删除这个领域吗?该操作不可撤销,其隶属于该领域的所有资源(帖子、聊天频道、发布者、制品等)都将被永久删除。三思而后行!"
}

View File

@@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:easy_localization_loader/easy_localization_loader.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart';
import 'package:relative_time/relative_time.dart';
import 'package:responsive_framework/responsive_framework.dart';
@@ -18,6 +19,8 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
await Hive.initFlutter();
if (!kReleaseMode) {
debugInvertOversizedImages = true;
}

View File

@@ -24,6 +24,14 @@ class NavigationProvider extends ChangeNotifier {
int? get currentIndex => _currentIndex;
static const List<String> kShowBottomNavScreen = [
'home',
'explore',
'account',
'album',
'chat',
];
static const List<AppNavDestination> kAllDestination = [
AppNavDestination(
icon: Icon(Symbols.home, weight: 400, opticalSize: 20),
@@ -35,26 +43,32 @@ class NavigationProvider extends ChangeNotifier {
screen: 'explore',
label: 'screenExplore',
),
AppNavDestination(
icon: Icon(Symbols.chat, weight: 400, opticalSize: 20),
screen: 'chat',
label: 'screenChat',
),
AppNavDestination(
icon: Icon(Symbols.account_circle, weight: 400, opticalSize: 20),
screen: 'account',
label: 'screenAccount',
),
AppNavDestination(
icon: Icon(Symbols.group, weight: 400, opticalSize: 20),
screen: 'realm',
label: 'screenRealm',
),
AppNavDestination(
icon: Icon(Symbols.album, weight: 400, opticalSize: 20),
screen: 'album',
label: 'screenAlbum',
),
AppNavDestination(
icon: Icon(Symbols.chat, weight: 400, opticalSize: 20),
screen: 'chat',
label: 'screenChat',
),
];
static const List<String> kDefaultPinnedDestination = [
'home',
'explore',
'account'
'chat',
'account',
];
List<AppNavDestination> destinations = [];

View File

@@ -1,3 +1,4 @@
import 'package:animations/animations.dart';
import 'package:go_router/go_router.dart';
import 'package:surface/screens/account.dart';
import 'package:surface/screens/account/profile_edit.dart';
@@ -8,56 +9,44 @@ import 'package:surface/screens/album.dart';
import 'package:surface/screens/auth/login.dart';
import 'package:surface/screens/auth/register.dart';
import 'package:surface/screens/chat.dart';
import 'package:surface/screens/chat/manage.dart';
import 'package:surface/screens/explore.dart';
import 'package:surface/screens/home.dart';
import 'package:surface/screens/post/post_detail.dart';
import 'package:surface/screens/post/post_editor.dart';
import 'package:surface/screens/realm.dart';
import 'package:surface/screens/realm/manage.dart';
import 'package:surface/screens/settings.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/navigation/app_background.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
final _appRoutes = [
ShellRoute(
builder: (context, state, child) => AppScaffold(
builder: (context, state, child) => AppPageScaffold(
body: child,
showBottomNavigation: true,
showAppBar: false,
),
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomeScreen(),
pageBuilder: (context, state) => NoTransitionPage(
child: const HomeScreen(),
),
),
GoRoute(
path: '/posts',
name: 'explore',
builder: (context, state) => const ExploreScreen(),
pageBuilder: (context, state) => NoTransitionPage(
child: const ExploreScreen(),
),
GoRoute(
path: '/account',
name: 'account',
builder: (context, state) => const AccountScreen(),
),
GoRoute(
path: '/chat',
name: 'chat',
builder: (context, state) => const ChatScreen(),
),
GoRoute(
path: '/album',
name: 'album',
builder: (context, state) => const AlbumScreen(),
),
],
),
ShellRoute(
builder: (context, state, child) => child,
routes: [
GoRoute(
path: '/post/write/:mode',
name: 'postEditor',
builder: (context, state) => PostEditorScreen(
pageBuilder: (context, state) => CustomTransitionPage(
child: PostEditorScreen(
mode: state.pathParameters['mode']!,
postEditId: int.tryParse(
state.uri.queryParameters['editing'] ?? '',
@@ -69,19 +58,110 @@ final _appRoutes = [
state.uri.queryParameters['reposting'] ?? '',
),
),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: AppBackground(isLessOptimization: true, child: child),
);
},
),
),
GoRoute(
path: '/post/:slug',
name: 'postDetail',
builder: (context, state) => PostDetailScreen(
pageBuilder: (context, state) => CustomTransitionPage(
child: PostDetailScreen(
slug: state.pathParameters['slug']!,
preload: state.extra as SnPost?,
),
)
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: AppBackground(isLessOptimization: true, child: child),
);
},
),
),
],
),
GoRoute(
path: '/account',
name: 'account',
pageBuilder: (context, state) => NoTransitionPage(
child: const AccountScreen(),
),
),
GoRoute(
path: '/chat',
name: 'chat',
pageBuilder: (context, state) => NoTransitionPage(
child: const ChatScreen(),
),
routes: [
GoRoute(
path: '/chat/manage',
name: 'chatManage',
pageBuilder: (context, state) => CustomTransitionPage(
child: ChatManageScreen(),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: AppBackground(
isLessOptimization: true,
child: child,
),
);
},
),
),
],
),
GoRoute(
path: '/realm',
name: 'realm',
pageBuilder: (context, state) => NoTransitionPage(
child: const RealmScreen(),
),
routes: [
GoRoute(
path: '/realm/manage',
name: 'realmManage',
pageBuilder: (context, state) => CustomTransitionPage(
child: RealmManageScreen(
editingRealmAlias: state.uri.queryParameters['editing'],
),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: AppBackground(
isLessOptimization: true,
child: child,
),
);
},
),
),
],
),
GoRoute(
path: '/album',
name: 'album',
pageBuilder: (context, state) => NoTransitionPage(
child: const AlbumScreen(),
),
),
],
),
ShellRoute(
builder: (context, state, child) => AppScaffold(body: child),
builder: (context, state, child) => AppPageScaffold(body: child),
routes: [
GoRoute(
path: '/auth/login',
@@ -118,7 +198,7 @@ final _appRoutes = [
],
),
ShellRoute(
builder: (context, state, child) => AppScaffold(body: child),
builder: (context, state, child) => AppPageScaffold(body: child),
routes: [
GoRoute(
path: '/settings',
@@ -132,8 +212,8 @@ final _appRoutes = [
final appRouter = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) => AppRootScaffold(body: child),
routes: _appRoutes,
builder: (context, state, child) => AppRootScaffold(body: child),
),
],
);

View File

@@ -148,20 +148,14 @@ class _AccountPublisherEditScreenState
mimetype: 'image/png',
);
if (!mounted) return;
final sn = context.read<SnNetworkProvider>();
await sn.client.put(
'/cgi/id/users/me/$place',
data: {'attachment': attachment.rid},
);
if (!mounted) return;
final ua = context.read<UserProvider>();
await ua.refreshUser();
if (!mounted) return;
context.showSnackbar('accountProfileEditApplied'.tr());
_syncWidget();
switch (place) {
case 'avatar':
_avatar = attachment.rid;
break;
case 'banner':
_banner = attachment.rid;
break;
}
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
@@ -286,7 +280,7 @@ class _AccountPublisherEditScreenState
],
)
],
).padding(horizontal: 16, vertical: 12),
).padding(horizontal: 24, vertical: 12),
),
);
}

View File

@@ -1,10 +1,88 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
class ChatScreen extends StatelessWidget {
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
bool _isBusy = false;
List<SnChannel>? _channels;
Future<void> _fetchChannels({scope = 'global', direct = false}) async {
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get(
'/cgi/im/channels/$scope/me/available',
queryParameters: {
'direct': direct,
},
);
_channels = List<SnChannel>.from(
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchChannels();
}
@override
Widget build(BuildContext context) {
return const Placeholder();
return Scaffold(
appBar: AppBar(
title: Text('screenChat').tr(),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Symbols.chat_add_on),
onPressed: () {
GoRouter.of(context).pushNamed('chatManage');
},
),
body: Column(
children: [
LoadingIndicator(isActive: _isBusy),
Expanded(
child: ListView.builder(
itemCount: _channels?.length ?? 0,
itemBuilder: (context, idx) {
final channel = _channels![idx];
return ListTile(
title: Text(channel.name),
subtitle: Text(channel.description),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(
content: null,
fallbackWidget: const Icon(Symbols.chat, size: 20),
),
);
},
),
),
],
),
);
}
}

View File

@@ -0,0 +1,292 @@
import 'package:dio/dio.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:uuid/uuid.dart';
class ChatManageScreen extends StatefulWidget {
final String? editingChannelAlias;
const ChatManageScreen({super.key, this.editingChannelAlias});
@override
State<ChatManageScreen> createState() => _ChatManageScreenState();
}
class _ChatManageScreenState extends State<ChatManageScreen> {
bool _isBusy = false;
final _aliasController = TextEditingController();
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
List<SnRealm>? _realms;
SnRealm? _belongToRealm;
Future<void> _fetchRealms() async {
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/realms/me/available');
_realms = List<SnRealm>.from(
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
);
} catch (err) {
if (mounted) context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
SnChannel? _editingChannel;
Future<void> _fetchChannel() async {
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get(
'/cgi/im/channels/${widget.editingChannelAlias}',
);
_editingChannel = SnChannel.fromJson(resp.data);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _performAction() async {
final uuid = const Uuid();
final sn = context.read<SnNetworkProvider>();
setState(() => _isBusy = true);
final scope = _belongToRealm != null ? _belongToRealm!.alias : 'global';
final payload = {
'alias': _aliasController.text.isNotEmpty
? _aliasController.text.toLowerCase()
: uuid.v4().replaceAll('-', '').substring(0, 12),
'name': _nameController.text,
'description': _descriptionController.text,
};
try {
final resp = await sn.client.request(
widget.editingChannelAlias != null
? '/cgi/im/channels/$scope/${widget.editingChannelAlias}'
: '/cgi/im/channels/$scope',
data: payload,
options: Options(
method: widget.editingChannelAlias != null ? 'PUT' : 'POST',
),
);
// ignore: use_build_context_synchronously
if (context.mounted) Navigator.pop(context, resp.data);
} catch (err) {
// ignore: use_build_context_synchronously
if (context.mounted) context.showErrorDialog(err);
}
setState(() => _isBusy = false);
}
@override
void initState() {
super.initState();
if (widget.editingChannelAlias != null) _fetchChannel();
_fetchRealms();
}
@override
void dispose() {
super.dispose();
_aliasController.dispose();
_nameController.dispose();
_descriptionController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: widget.editingChannelAlias != null
? Text('screenChatManage').tr()
: Text('screenChatNew').tr(),
),
body: SingleChildScrollView(
child: Column(
children: [
LoadingIndicator(isActive: _isBusy),
if (_editingChannel != null)
MaterialBanner(
leading: const Icon(Icons.edit),
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text(
'channelEditingNotice'
.tr(args: ['#${_editingChannel!.alias}']),
),
actions: [
TextButton(
child: Text('cancel').tr(),
onPressed: () {
Navigator.pop(context);
},
),
],
),
DropdownButtonHideUnderline(
child: DropdownButton2<SnRealm>(
isExpanded: true,
hint: Text(
'fieldChatBelongToRealm'.tr(),
style: TextStyle(
color: Theme.of(context).hintColor,
),
),
items: [
...(_realms?.map(
(SnRealm item) => DropdownMenuItem<SnRealm>(
value: item,
child: Row(
children: [
AccountImage(
content: item.avatar,
radius: 16,
fallbackWidget: const Icon(
Symbols.group,
size: 16,
),
),
const Gap(12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.name).textStyle(Theme.of(context)
.textTheme
.bodyMedium!),
Text(
item.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
).textStyle(
Theme.of(context).textTheme.bodySmall!),
],
),
),
],
),
),
) ??
[]),
DropdownMenuItem<SnRealm>(
value: null,
child: Row(
children: [
CircleAvatar(
radius: 16,
backgroundColor: Colors.transparent,
foregroundColor:
Theme.of(context).colorScheme.onSurface,
child: const Icon(Symbols.clear),
),
const Gap(12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('fieldChatBelongToRealmUnset')
.tr()
.textStyle(
Theme.of(context).textTheme.bodyMedium!,
),
],
),
),
],
),
),
],
value: _belongToRealm,
onChanged: (SnRealm? value) {
setState(() => _belongToRealm = value);
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.only(right: 16),
height: 60,
),
menuItemStyleData: const MenuItemStyleData(
height: 60,
),
),
),
const Divider(height: 1),
const Gap(12),
Column(
children: [
TextField(
controller: _aliasController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldChatAlias'.tr(),
helperText: 'fieldChatAliasHint'.tr(),
helperMaxLines: 2,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _nameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldChatName'.tr(),
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _descriptionController,
maxLines: null,
minLines: 3,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldChatDescription'.tr(),
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton.icon(
onPressed: _isBusy ? null : _performAction,
icon: const Icon(Symbols.save),
label: Text('apply').tr(),
),
],
),
],
).padding(horizontal: 24),
],
),
),
);
}
}

220
lib/screens/realm.dart Normal file
View File

@@ -0,0 +1,220 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/universal_image.dart';
class RealmScreen extends StatefulWidget {
const RealmScreen({super.key});
@override
State<RealmScreen> createState() => _RealmScreenState();
}
class _RealmScreenState extends State<RealmScreen> {
bool _isBusy = false;
bool _isCompactView = false;
List<SnRealm>? _realms;
Future<void> _fetchRealms() async {
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/id/realms/me/available');
_realms = List<SnRealm>.from(
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
);
} catch (err) {
if (mounted) context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _deleteRealm(SnRealm realm) async {
final confirm = await context.showConfirmDialog(
'realmDelete'.tr(args: ['#${realm.alias}']),
'realmDeleteDescription'.tr(),
);
if (!confirm) return;
if (!mounted) return;
final sn = context.read<SnNetworkProvider>();
setState(() => _isBusy = true);
try {
await sn.client.delete('/cgi/id/realms/${realm.alias}');
if (!mounted) return;
context.showSnackbar('realmDeleted'.tr(args: ['#${realm.alias}']));
_fetchRealms();
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchRealms();
}
@override
Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>();
return Scaffold(
appBar: AppBar(
title: Text('screenRealm').tr(),
actions: [
IconButton(
icon: !_isCompactView
? const Icon(Icons.view_list)
: const Icon(Icons.view_module),
onPressed: () {
setState(() => _isCompactView = !_isCompactView);
},
),
],
),
floatingActionButton: FloatingActionButton(
child: const Icon(Symbols.group_add),
onPressed: () {
GoRouter.of(context).pushNamed('realmManage');
},
),
body: Column(
children: [
LoadingIndicator(isActive: _isBusy),
Expanded(
child: RefreshIndicator(
onRefresh: _fetchRealms,
child: ListView.builder(
itemCount: _realms?.length ?? 0,
itemBuilder: (context, idx) {
final realm = _realms![idx];
if (_isCompactView) {
return ListTile(
contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
leading: AccountImage(
content: realm.avatar,
fallbackWidget: const Icon(Symbols.group, size: 20),
),
title: Text(realm.name),
subtitle: Text(
realm.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: PopupMenuButton(
itemBuilder: (BuildContext context) => [
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.edit),
const Gap(16),
Text('edit').tr(),
],
),
onTap: () {
GoRouter.of(context).pushNamed(
'realmManage',
queryParameters: {'editing': realm.alias},
).then((value) {
if (value != null) {
_fetchRealms();
}
});
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.delete),
const Gap(16),
Text('delete').tr(),
],
),
onTap: () {
_deleteRealm(realm);
},
),
],
),
);
}
return Card(
margin: const EdgeInsets.all(12),
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: [
Container(
color: Theme.of(context)
.colorScheme
.surfaceContainer,
child: (realm.banner?.isEmpty ?? true)
? const SizedBox.shrink()
: AutoResizeUniversalImage(
sn.getAttachmentUrl(realm.banner!),
fit: BoxFit.cover,
),
),
Positioned(
bottom: -30,
left: 18,
child: AccountImage(
content: realm.avatar,
radius: 24,
fallbackWidget:
const Icon(Symbols.group, size: 24),
),
),
],
),
),
const Gap(20 + 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(realm.name).textStyle(
Theme.of(context).textTheme.titleMedium!),
Text(realm.description).textStyle(
Theme.of(context).textTheme.bodySmall!),
],
).padding(horizontal: 24, bottom: 14),
],
),
onTap: () {},
),
);
},
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,312 @@
import 'dart:io';
import 'dart:ui';
import 'package:croppy/croppy.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:image_picker/image_picker.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:path/path.dart' show basename;
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/realm.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/universal_image.dart';
import 'package:uuid/uuid.dart';
class RealmManageScreen extends StatefulWidget {
final String? editingRealmAlias;
const RealmManageScreen({super.key, this.editingRealmAlias});
@override
State<RealmManageScreen> createState() => _RealmManageScreenState();
}
class _RealmManageScreenState extends State<RealmManageScreen> {
bool _isBusy = false;
SnRealm? _editingRealm;
Future<void> _fetchRealm() async {
final sn = context.read<SnNetworkProvider>();
setState(() => _isBusy = true);
try {
final resp =
await sn.client.get('/cgi/id/realms/${widget.editingRealmAlias}');
final out = SnRealm.fromJson(resp.data);
_editingRealm = out;
_avatar = out.avatar;
_banner = out.banner;
_aliasController.text = out.alias;
_nameController.text = out.name;
_descriptionController.text = out.description;
} catch (err) {
// ignore: use_build_context_synchronously
if (context.mounted) context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
String? _avatar;
String? _banner;
final _aliasController = TextEditingController();
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
final _imagePicker = ImagePicker();
Future<void> _updateImage(String place) async {
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
if (image == null) return;
if (!mounted) return;
final ImageProvider imageProvider =
kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path));
final aspectRatios = place == 'banner'
? [CropAspectRatio(width: 16, height: 7)]
: [CropAspectRatio(width: 1, height: 1)];
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
? await showCupertinoImageCropper(
// ignore: use_build_context_synchronously
context,
allowedAspectRatios: aspectRatios,
imageProvider: imageProvider,
)
: await showMaterialImageCropper(
// ignore: use_build_context_synchronously
context,
allowedAspectRatios: aspectRatios,
imageProvider: imageProvider,
);
if (result == null) return;
if (!mounted) return;
final attach = context.read<SnAttachmentProvider>();
setState(() => _isBusy = true);
final rawBytes =
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
.buffer
.asUint8List();
try {
final attachment = await attach.directUploadOne(
rawBytes,
basename(image.path),
'avatar',
null,
mimetype: 'image/png',
);
switch (place) {
case 'avatar':
_avatar = attachment.rid;
break;
case 'banner':
_banner = attachment.rid;
break;
}
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _performAction() async {
final uuid = const Uuid();
final payload = {
'alias': _aliasController.text.isNotEmpty
? _aliasController.text.toLowerCase()
: uuid.v4().replaceAll('-', '').substring(0, 12),
'name': _nameController.text,
'description': _descriptionController.text,
'avatar': _avatar,
'banner': _banner,
};
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.request(
widget.editingRealmAlias != null
? '/cgi/id/realms/${widget.editingRealmAlias}'
: '/cgi/id/realms',
data: payload,
options: Options(
method: widget.editingRealmAlias != null ? 'PUT' : 'POST',
),
);
final out = SnRealm.fromJson(resp.data);
// ignore: use_build_context_synchronously
if (context.mounted) Navigator.pop(context, out);
} catch (err) {
// ignore: use_build_context_synchronously
if (context.mounted) context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
if (widget.editingRealmAlias != null) _fetchRealm();
}
@override
void dispose() {
_aliasController.dispose();
_nameController.dispose();
_descriptionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>();
return Scaffold(
appBar: AppBar(
title: widget.editingRealmAlias != null
? Text('screenRealmManage').tr()
: Text('screenRealmNew').tr(),
),
body: SingleChildScrollView(
child: Column(
children: [
LoadingIndicator(isActive: _isBusy),
if (_editingRealm != null)
MaterialBanner(
leading: const Icon(Icons.edit),
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
dividerColor: Colors.transparent,
content: Text(
'realmEditingNotice'.tr(args: ['#${_editingRealm!.alias}']),
),
actions: [
TextButton(
child: Text('cancel').tr(),
onPressed: () {
Navigator.pop(context);
},
),
],
),
const Gap(24),
Stack(
clipBehavior: Clip.none,
children: [
Material(
elevation: 0,
child: InkWell(
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
color: Theme.of(context)
.colorScheme
.surfaceContainerHigh,
child: _banner != null
? AutoResizeUniversalImage(
sn.getAttachmentUrl(_banner!),
fit: BoxFit.cover,
)
: const SizedBox.shrink(),
),
),
),
onTap: () {
_updateImage('banner');
},
),
),
Positioned(
bottom: -28,
left: 16,
child: Material(
elevation: 2,
borderRadius: const BorderRadius.all(Radius.circular(40)),
child: InkWell(
child: AccountImage(
content: _avatar,
radius: 40,
fallbackWidget: const Icon(Symbols.group, size: 40),
),
onTap: () {
_updateImage('avatar');
},
),
),
),
],
).padding(horizontal: 24),
const Gap(8 + 28),
Column(
children: [
TextField(
controller: _aliasController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldRealmAlias'.tr(),
helperText: 'fieldRealmAliasHint'.tr(),
helperMaxLines: 2,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _nameController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldRealmName'.tr(),
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(4),
TextField(
controller: _descriptionController,
maxLines: null,
minLines: 3,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: 'fieldRealmDescription'.tr(),
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton.icon(
onPressed: _isBusy ? null : _performAction,
icon: const Icon(Symbols.save),
label: Text('apply').tr(),
),
],
),
],
).padding(horizontal: 24 + 8),
],
),
),
);
}
}

30
lib/types/chat.dart Normal file
View File

@@ -0,0 +1,30 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:surface/types/realm.dart';
part 'chat.freezed.dart';
part 'chat.g.dart';
@freezed
class SnChannel with _$SnChannel {
const factory SnChannel({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required dynamic deletedAt,
required String alias,
required String name,
required String description,
required List<dynamic> members,
required dynamic messages,
required dynamic calls,
required int type,
required int accountId,
required bool isPublic,
required bool isCommunity,
required SnRealm? realm,
required int? realmId,
}) = _SnChannel;
factory SnChannel.fromJson(Map<String, dynamic> json) =>
_$SnChannelFromJson(json);
}

516
lib/types/chat.freezed.dart Normal file
View File

@@ -0,0 +1,516 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'chat.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
SnChannel _$SnChannelFromJson(Map<String, dynamic> json) {
return _SnChannel.fromJson(json);
}
/// @nodoc
mixin _$SnChannel {
int get id => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
dynamic get deletedAt => throw _privateConstructorUsedError;
String get alias => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get description => throw _privateConstructorUsedError;
List<dynamic> get members => throw _privateConstructorUsedError;
dynamic get messages => throw _privateConstructorUsedError;
dynamic get calls => throw _privateConstructorUsedError;
int get type => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError;
bool get isPublic => throw _privateConstructorUsedError;
bool get isCommunity => throw _privateConstructorUsedError;
SnRealm? get realm => throw _privateConstructorUsedError;
int? get realmId => throw _privateConstructorUsedError;
/// Serializes this SnChannel to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnChannel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnChannelCopyWith<SnChannel> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnChannelCopyWith<$Res> {
factory $SnChannelCopyWith(SnChannel value, $Res Function(SnChannel) then) =
_$SnChannelCopyWithImpl<$Res, SnChannel>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
dynamic deletedAt,
String alias,
String name,
String description,
List<dynamic> members,
dynamic messages,
dynamic calls,
int type,
int accountId,
bool isPublic,
bool isCommunity,
SnRealm? realm,
int? realmId});
$SnRealmCopyWith<$Res>? get realm;
}
/// @nodoc
class _$SnChannelCopyWithImpl<$Res, $Val extends SnChannel>
implements $SnChannelCopyWith<$Res> {
_$SnChannelCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SnChannel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? alias = null,
Object? name = null,
Object? description = null,
Object? members = null,
Object? messages = freezed,
Object? calls = freezed,
Object? type = null,
Object? accountId = null,
Object? isPublic = null,
Object? isCommunity = null,
Object? realm = freezed,
Object? realmId = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as dynamic,
alias: null == alias
? _value.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
members: null == members
? _value.members
: members // ignore: cast_nullable_to_non_nullable
as List<dynamic>,
messages: freezed == messages
? _value.messages
: messages // ignore: cast_nullable_to_non_nullable
as dynamic,
calls: freezed == calls
? _value.calls
: calls // ignore: cast_nullable_to_non_nullable
as dynamic,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as int,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
isPublic: null == isPublic
? _value.isPublic
: isPublic // ignore: cast_nullable_to_non_nullable
as bool,
isCommunity: null == isCommunity
? _value.isCommunity
: isCommunity // ignore: cast_nullable_to_non_nullable
as bool,
realm: freezed == realm
? _value.realm
: realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,
realmId: freezed == realmId
? _value.realmId
: realmId // ignore: cast_nullable_to_non_nullable
as int?,
) as $Val);
}
/// Create a copy of SnChannel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnRealmCopyWith<$Res>? get realm {
if (_value.realm == null) {
return null;
}
return $SnRealmCopyWith<$Res>(_value.realm!, (value) {
return _then(_value.copyWith(realm: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$SnChannelImplCopyWith<$Res>
implements $SnChannelCopyWith<$Res> {
factory _$$SnChannelImplCopyWith(
_$SnChannelImpl value, $Res Function(_$SnChannelImpl) then) =
__$$SnChannelImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
dynamic deletedAt,
String alias,
String name,
String description,
List<dynamic> members,
dynamic messages,
dynamic calls,
int type,
int accountId,
bool isPublic,
bool isCommunity,
SnRealm? realm,
int? realmId});
@override
$SnRealmCopyWith<$Res>? get realm;
}
/// @nodoc
class __$$SnChannelImplCopyWithImpl<$Res>
extends _$SnChannelCopyWithImpl<$Res, _$SnChannelImpl>
implements _$$SnChannelImplCopyWith<$Res> {
__$$SnChannelImplCopyWithImpl(
_$SnChannelImpl _value, $Res Function(_$SnChannelImpl) _then)
: super(_value, _then);
/// Create a copy of SnChannel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? alias = null,
Object? name = null,
Object? description = null,
Object? members = null,
Object? messages = freezed,
Object? calls = freezed,
Object? type = null,
Object? accountId = null,
Object? isPublic = null,
Object? isCommunity = null,
Object? realm = freezed,
Object? realmId = freezed,
}) {
return _then(_$SnChannelImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as dynamic,
alias: null == alias
? _value.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
members: null == members
? _value._members
: members // ignore: cast_nullable_to_non_nullable
as List<dynamic>,
messages: freezed == messages
? _value.messages
: messages // ignore: cast_nullable_to_non_nullable
as dynamic,
calls: freezed == calls
? _value.calls
: calls // ignore: cast_nullable_to_non_nullable
as dynamic,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
as int,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
isPublic: null == isPublic
? _value.isPublic
: isPublic // ignore: cast_nullable_to_non_nullable
as bool,
isCommunity: null == isCommunity
? _value.isCommunity
: isCommunity // ignore: cast_nullable_to_non_nullable
as bool,
realm: freezed == realm
? _value.realm
: realm // ignore: cast_nullable_to_non_nullable
as SnRealm?,
realmId: freezed == realmId
? _value.realmId
: realmId // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnChannelImpl implements _SnChannel {
const _$SnChannelImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.alias,
required this.name,
required this.description,
required final List<dynamic> members,
required this.messages,
required this.calls,
required this.type,
required this.accountId,
required this.isPublic,
required this.isCommunity,
required this.realm,
required this.realmId})
: _members = members;
factory _$SnChannelImpl.fromJson(Map<String, dynamic> json) =>
_$$SnChannelImplFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final dynamic deletedAt;
@override
final String alias;
@override
final String name;
@override
final String description;
final List<dynamic> _members;
@override
List<dynamic> get members {
if (_members is EqualUnmodifiableListView) return _members;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_members);
}
@override
final dynamic messages;
@override
final dynamic calls;
@override
final int type;
@override
final int accountId;
@override
final bool isPublic;
@override
final bool isCommunity;
@override
final SnRealm? realm;
@override
final int? realmId;
@override
String toString() {
return 'SnChannel(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, messages: $messages, calls: $calls, type: $type, accountId: $accountId, isPublic: $isPublic, isCommunity: $isCommunity, realm: $realm, realmId: $realmId)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnChannelImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
const DeepCollectionEquality().equals(other.deletedAt, deletedAt) &&
(identical(other.alias, alias) || other.alias == alias) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
const DeepCollectionEquality().equals(other._members, _members) &&
const DeepCollectionEquality().equals(other.messages, messages) &&
const DeepCollectionEquality().equals(other.calls, calls) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId) &&
(identical(other.isPublic, isPublic) ||
other.isPublic == isPublic) &&
(identical(other.isCommunity, isCommunity) ||
other.isCommunity == isCommunity) &&
(identical(other.realm, realm) || other.realm == realm) &&
(identical(other.realmId, realmId) || other.realmId == realmId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
const DeepCollectionEquality().hash(deletedAt),
alias,
name,
description,
const DeepCollectionEquality().hash(_members),
const DeepCollectionEquality().hash(messages),
const DeepCollectionEquality().hash(calls),
type,
accountId,
isPublic,
isCommunity,
realm,
realmId);
/// Create a copy of SnChannel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnChannelImplCopyWith<_$SnChannelImpl> get copyWith =>
__$$SnChannelImplCopyWithImpl<_$SnChannelImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnChannelImplToJson(
this,
);
}
}
abstract class _SnChannel implements SnChannel {
const factory _SnChannel(
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final dynamic deletedAt,
required final String alias,
required final String name,
required final String description,
required final List<dynamic> members,
required final dynamic messages,
required final dynamic calls,
required final int type,
required final int accountId,
required final bool isPublic,
required final bool isCommunity,
required final SnRealm? realm,
required final int? realmId}) = _$SnChannelImpl;
factory _SnChannel.fromJson(Map<String, dynamic> json) =
_$SnChannelImpl.fromJson;
@override
int get id;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
dynamic get deletedAt;
@override
String get alias;
@override
String get name;
@override
String get description;
@override
List<dynamic> get members;
@override
dynamic get messages;
@override
dynamic get calls;
@override
int get type;
@override
int get accountId;
@override
bool get isPublic;
@override
bool get isCommunity;
@override
SnRealm? get realm;
@override
int? get realmId;
/// Create a copy of SnChannel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnChannelImplCopyWith<_$SnChannelImpl> get copyWith =>
throw _privateConstructorUsedError;
}

49
lib/types/chat.g.dart Normal file
View File

@@ -0,0 +1,49 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$SnChannelImpl _$$SnChannelImplFromJson(Map<String, dynamic> json) =>
_$SnChannelImpl(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'],
alias: json['alias'] as String,
name: json['name'] as String,
description: json['description'] as String,
members: json['members'] as List<dynamic>,
messages: json['messages'],
calls: json['calls'],
type: (json['type'] as num).toInt(),
accountId: (json['account_id'] as num).toInt(),
isPublic: json['is_public'] as bool,
isCommunity: json['is_community'] as bool,
realm: json['realm'] == null
? null
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
realmId: (json['realm_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$$SnChannelImplToJson(_$SnChannelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt,
'alias': instance.alias,
'name': instance.name,
'description': instance.description,
'members': instance.members,
'messages': instance.messages,
'calls': instance.calls,
'type': instance.type,
'account_id': instance.accountId,
'is_public': instance.isPublic,
'is_community': instance.isCommunity,
'realm': instance.realm?.toJson(),
'realm_id': instance.realmId,
};

46
lib/types/realm.dart Normal file
View File

@@ -0,0 +1,46 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:surface/types/account.dart';
part 'realm.freezed.dart';
part 'realm.g.dart';
@freezed
class SnRealmMember with _$SnRealmMember {
const factory SnRealmMember({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required int realmId,
required int accountId,
required SnRealm realm,
required SnAccount account,
required int powerLevel,
}) = _SnRealmMember;
factory SnRealmMember.fromJson(Map<String, dynamic> json) =>
_$SnRealmMemberFromJson(json);
}
@freezed
class SnRealm with _$SnRealm {
const factory SnRealm({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String alias,
required String name,
required String description,
required List<SnRealmMember>? members,
required String? avatar,
required String? banner,
required Map<String, dynamic>? accessPolicy,
required bool isPublic,
required bool isCommunity,
required int accountId,
}) = _SnRealm;
factory SnRealm.fromJson(Map<String, dynamic> json) =>
_$SnRealmFromJson(json);
}

View File

@@ -0,0 +1,812 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
SnRealmMember _$SnRealmMemberFromJson(Map<String, dynamic> json) {
return _SnRealmMember.fromJson(json);
}
/// @nodoc
mixin _$SnRealmMember {
int get id => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
DateTime? get deletedAt => throw _privateConstructorUsedError;
int get realmId => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError;
SnRealm get realm => throw _privateConstructorUsedError;
SnAccount get account => throw _privateConstructorUsedError;
int get powerLevel => throw _privateConstructorUsedError;
/// Serializes this SnRealmMember to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnRealmMemberCopyWith<SnRealmMember> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnRealmMemberCopyWith<$Res> {
factory $SnRealmMemberCopyWith(
SnRealmMember value, $Res Function(SnRealmMember) then) =
_$SnRealmMemberCopyWithImpl<$Res, SnRealmMember>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
int realmId,
int accountId,
SnRealm realm,
SnAccount account,
int powerLevel});
$SnRealmCopyWith<$Res> get realm;
$SnAccountCopyWith<$Res> get account;
}
/// @nodoc
class _$SnRealmMemberCopyWithImpl<$Res, $Val extends SnRealmMember>
implements $SnRealmMemberCopyWith<$Res> {
_$SnRealmMemberCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _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? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? realmId = null,
Object? accountId = null,
Object? realm = null,
Object? account = null,
Object? powerLevel = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
realmId: null == realmId
? _value.realmId
: realmId // ignore: cast_nullable_to_non_nullable
as int,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
realm: null == realm
? _value.realm
: realm // ignore: cast_nullable_to_non_nullable
as SnRealm,
account: null == account
? _value.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount,
powerLevel: null == powerLevel
? _value.powerLevel
: powerLevel // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
/// 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 {
return $SnRealmCopyWith<$Res>(_value.realm, (value) {
return _then(_value.copyWith(realm: value) as $Val);
});
}
/// 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 {
return $SnAccountCopyWith<$Res>(_value.account, (value) {
return _then(_value.copyWith(account: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$SnRealmMemberImplCopyWith<$Res>
implements $SnRealmMemberCopyWith<$Res> {
factory _$$SnRealmMemberImplCopyWith(
_$SnRealmMemberImpl value, $Res Function(_$SnRealmMemberImpl) then) =
__$$SnRealmMemberImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
int realmId,
int accountId,
SnRealm realm,
SnAccount account,
int powerLevel});
@override
$SnRealmCopyWith<$Res> get realm;
@override
$SnAccountCopyWith<$Res> get account;
}
/// @nodoc
class __$$SnRealmMemberImplCopyWithImpl<$Res>
extends _$SnRealmMemberCopyWithImpl<$Res, _$SnRealmMemberImpl>
implements _$$SnRealmMemberImplCopyWith<$Res> {
__$$SnRealmMemberImplCopyWithImpl(
_$SnRealmMemberImpl _value, $Res Function(_$SnRealmMemberImpl) _then)
: super(_value, _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? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? realmId = null,
Object? accountId = null,
Object? realm = null,
Object? account = null,
Object? powerLevel = null,
}) {
return _then(_$SnRealmMemberImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
realmId: null == realmId
? _value.realmId
: realmId // ignore: cast_nullable_to_non_nullable
as int,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
realm: null == realm
? _value.realm
: realm // ignore: cast_nullable_to_non_nullable
as SnRealm,
account: null == account
? _value.account
: account // ignore: cast_nullable_to_non_nullable
as SnAccount,
powerLevel: null == powerLevel
? _value.powerLevel
: powerLevel // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnRealmMemberImpl implements _SnRealmMember {
const _$SnRealmMemberImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.realmId,
required this.accountId,
required this.realm,
required this.account,
required this.powerLevel});
factory _$SnRealmMemberImpl.fromJson(Map<String, dynamic> json) =>
_$$SnRealmMemberImplFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final int realmId;
@override
final int accountId;
@override
final SnRealm realm;
@override
final SnAccount account;
@override
final int powerLevel;
@override
String toString() {
return 'SnRealmMember(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, accountId: $accountId, realm: $realm, account: $account, powerLevel: $powerLevel)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnRealmMemberImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) &&
(identical(other.realmId, realmId) || other.realmId == realmId) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId) &&
(identical(other.realm, realm) || other.realm == realm) &&
(identical(other.account, account) || other.account == account) &&
(identical(other.powerLevel, powerLevel) ||
other.powerLevel == powerLevel));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
deletedAt, realmId, accountId, realm, account, powerLevel);
/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnRealmMemberImplCopyWith<_$SnRealmMemberImpl> get copyWith =>
__$$SnRealmMemberImplCopyWithImpl<_$SnRealmMemberImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnRealmMemberImplToJson(
this,
);
}
}
abstract class _SnRealmMember implements SnRealmMember {
const factory _SnRealmMember(
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final int realmId,
required final int accountId,
required final SnRealm realm,
required final SnAccount account,
required final int powerLevel}) = _$SnRealmMemberImpl;
factory _SnRealmMember.fromJson(Map<String, dynamic> json) =
_$SnRealmMemberImpl.fromJson;
@override
int get id;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
DateTime? get deletedAt;
@override
int get realmId;
@override
int get accountId;
@override
SnRealm get realm;
@override
SnAccount get account;
@override
int get powerLevel;
/// Create a copy of SnRealmMember
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnRealmMemberImplCopyWith<_$SnRealmMemberImpl> get copyWith =>
throw _privateConstructorUsedError;
}
SnRealm _$SnRealmFromJson(Map<String, dynamic> json) {
return _SnRealm.fromJson(json);
}
/// @nodoc
mixin _$SnRealm {
int get id => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
DateTime? get deletedAt => throw _privateConstructorUsedError;
String get alias => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get description => throw _privateConstructorUsedError;
List<SnRealmMember>? get members => throw _privateConstructorUsedError;
String? get avatar => throw _privateConstructorUsedError;
String? get banner => throw _privateConstructorUsedError;
Map<String, dynamic>? get accessPolicy => throw _privateConstructorUsedError;
bool get isPublic => throw _privateConstructorUsedError;
bool get isCommunity => throw _privateConstructorUsedError;
int get accountId => throw _privateConstructorUsedError;
/// Serializes this SnRealm to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SnRealmCopyWith<SnRealm> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SnRealmCopyWith<$Res> {
factory $SnRealmCopyWith(SnRealm value, $Res Function(SnRealm) then) =
_$SnRealmCopyWithImpl<$Res, SnRealm>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
String description,
List<SnRealmMember>? members,
String? avatar,
String? banner,
Map<String, dynamic>? accessPolicy,
bool isPublic,
bool isCommunity,
int accountId});
}
/// @nodoc
class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
implements $SnRealmCopyWith<$Res> {
_$SnRealmCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _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? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? alias = null,
Object? name = null,
Object? description = null,
Object? members = freezed,
Object? avatar = freezed,
Object? banner = freezed,
Object? accessPolicy = freezed,
Object? isPublic = null,
Object? isCommunity = null,
Object? accountId = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
alias: null == alias
? _value.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
members: freezed == members
? _value.members
: members // ignore: cast_nullable_to_non_nullable
as List<SnRealmMember>?,
avatar: freezed == avatar
? _value.avatar
: avatar // ignore: cast_nullable_to_non_nullable
as String?,
banner: freezed == banner
? _value.banner
: banner // ignore: cast_nullable_to_non_nullable
as String?,
accessPolicy: freezed == accessPolicy
? _value.accessPolicy
: accessPolicy // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
isPublic: null == isPublic
? _value.isPublic
: isPublic // ignore: cast_nullable_to_non_nullable
as bool,
isCommunity: null == isCommunity
? _value.isCommunity
: isCommunity // ignore: cast_nullable_to_non_nullable
as bool,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$SnRealmImplCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
factory _$$SnRealmImplCopyWith(
_$SnRealmImpl value, $Res Function(_$SnRealmImpl) then) =
__$$SnRealmImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
String description,
List<SnRealmMember>? members,
String? avatar,
String? banner,
Map<String, dynamic>? accessPolicy,
bool isPublic,
bool isCommunity,
int accountId});
}
/// @nodoc
class __$$SnRealmImplCopyWithImpl<$Res>
extends _$SnRealmCopyWithImpl<$Res, _$SnRealmImpl>
implements _$$SnRealmImplCopyWith<$Res> {
__$$SnRealmImplCopyWithImpl(
_$SnRealmImpl _value, $Res Function(_$SnRealmImpl) _then)
: super(_value, _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? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? alias = null,
Object? name = null,
Object? description = null,
Object? members = freezed,
Object? avatar = freezed,
Object? banner = freezed,
Object? accessPolicy = freezed,
Object? isPublic = null,
Object? isCommunity = null,
Object? accountId = null,
}) {
return _then(_$SnRealmImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,
deletedAt: freezed == deletedAt
? _value.deletedAt
: deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
alias: null == alias
? _value.alias
: alias // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
members: freezed == members
? _value._members
: members // ignore: cast_nullable_to_non_nullable
as List<SnRealmMember>?,
avatar: freezed == avatar
? _value.avatar
: avatar // ignore: cast_nullable_to_non_nullable
as String?,
banner: freezed == banner
? _value.banner
: banner // ignore: cast_nullable_to_non_nullable
as String?,
accessPolicy: freezed == accessPolicy
? _value._accessPolicy
: accessPolicy // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
isPublic: null == isPublic
? _value.isPublic
: isPublic // ignore: cast_nullable_to_non_nullable
as bool,
isCommunity: null == isCommunity
? _value.isCommunity
: isCommunity // ignore: cast_nullable_to_non_nullable
as bool,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnRealmImpl implements _SnRealm {
const _$SnRealmImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.alias,
required this.name,
required this.description,
required final List<SnRealmMember>? members,
required this.avatar,
required this.banner,
required final Map<String, dynamic>? accessPolicy,
required this.isPublic,
required this.isCommunity,
required this.accountId})
: _members = members,
_accessPolicy = accessPolicy;
factory _$SnRealmImpl.fromJson(Map<String, dynamic> json) =>
_$$SnRealmImplFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final String alias;
@override
final String name;
@override
final String description;
final List<SnRealmMember>? _members;
@override
List<SnRealmMember>? get members {
final value = _members;
if (value == null) return null;
if (_members is EqualUnmodifiableListView) return _members;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
final String? avatar;
@override
final String? banner;
final Map<String, dynamic>? _accessPolicy;
@override
Map<String, dynamic>? get accessPolicy {
final value = _accessPolicy;
if (value == null) return null;
if (_accessPolicy is EqualUnmodifiableMapView) return _accessPolicy;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override
final bool isPublic;
@override
final bool isCommunity;
@override
final int accountId;
@override
String toString() {
return 'SnRealm(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, avatar: $avatar, banner: $banner, accessPolicy: $accessPolicy, isPublic: $isPublic, isCommunity: $isCommunity, accountId: $accountId)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SnRealmImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) &&
(identical(other.alias, alias) || other.alias == alias) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
const DeepCollectionEquality().equals(other._members, _members) &&
(identical(other.avatar, avatar) || other.avatar == avatar) &&
(identical(other.banner, banner) || other.banner == banner) &&
const DeepCollectionEquality()
.equals(other._accessPolicy, _accessPolicy) &&
(identical(other.isPublic, isPublic) ||
other.isPublic == isPublic) &&
(identical(other.isCommunity, isCommunity) ||
other.isCommunity == isCommunity) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
alias,
name,
description,
const DeepCollectionEquality().hash(_members),
avatar,
banner,
const DeepCollectionEquality().hash(_accessPolicy),
isPublic,
isCommunity,
accountId);
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SnRealmImplCopyWith<_$SnRealmImpl> get copyWith =>
__$$SnRealmImplCopyWithImpl<_$SnRealmImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SnRealmImplToJson(
this,
);
}
}
abstract class _SnRealm implements SnRealm {
const factory _SnRealm(
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final String alias,
required final String name,
required final String description,
required final List<SnRealmMember>? members,
required final String? avatar,
required final String? banner,
required final Map<String, dynamic>? accessPolicy,
required final bool isPublic,
required final bool isCommunity,
required final int accountId}) = _$SnRealmImpl;
factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson;
@override
int get id;
@override
DateTime get createdAt;
@override
DateTime get updatedAt;
@override
DateTime? get deletedAt;
@override
String get alias;
@override
String get name;
@override
String get description;
@override
List<SnRealmMember>? get members;
@override
String? get avatar;
@override
String? get banner;
@override
Map<String, dynamic>? get accessPolicy;
@override
bool get isPublic;
@override
bool get isCommunity;
@override
int get accountId;
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SnRealmImplCopyWith<_$SnRealmImpl> get copyWith =>
throw _privateConstructorUsedError;
}

75
lib/types/realm.g.dart Normal file
View File

@@ -0,0 +1,75 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'realm.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$SnRealmMemberImpl _$$SnRealmMemberImplFromJson(Map<String, dynamic> json) =>
_$SnRealmMemberImpl(
id: (json['id'] as num).toInt(),
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),
realmId: (json['realm_id'] as num).toInt(),
accountId: (json['account_id'] as num).toInt(),
realm: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
account: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
powerLevel: (json['power_level'] as num).toInt(),
);
Map<String, dynamic> _$$SnRealmMemberImplToJson(_$SnRealmMemberImpl instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'realm_id': instance.realmId,
'account_id': instance.accountId,
'realm': instance.realm.toJson(),
'account': instance.account.toJson(),
'power_level': instance.powerLevel,
};
_$SnRealmImpl _$$SnRealmImplFromJson(Map<String, dynamic> json) =>
_$SnRealmImpl(
id: (json['id'] as num).toInt(),
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),
alias: json['alias'] as String,
name: json['name'] as String,
description: json['description'] as String,
members: (json['members'] as List<dynamic>?)
?.map((e) => SnRealmMember.fromJson(e as Map<String, dynamic>))
.toList(),
avatar: json['avatar'] as String?,
banner: json['banner'] as String?,
accessPolicy: json['access_policy'] as Map<String, dynamic>?,
isPublic: json['is_public'] as bool,
isCommunity: json['is_community'] as bool,
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'alias': instance.alias,
'name': instance.name,
'description': instance.description,
'members': instance.members?.map((e) => e.toJson()).toList(),
'avatar': instance.avatar,
'banner': instance.banner,
'access_policy': instance.accessPolicy,
'is_public': instance.isPublic,
'is_community': instance.isCommunity,
'account_id': instance.accountId,
};

View File

@@ -42,7 +42,7 @@ class ConnectionIndicator extends StatelessWidget {
)
.height(
(ws.isBusy || !ws.isConnected) && ua.isAuthorized
? MediaQuery.of(context).padding.top + 30
? MediaQuery.of(context).padding.top + 36
: 0,
animate: true)
.animate(

View File

@@ -6,21 +6,44 @@ import 'package:path_provider/path_provider.dart';
class AppBackground extends StatelessWidget {
final Widget child;
const AppBackground({super.key, required this.child});
final bool isLessOptimization;
const AppBackground({
super.key,
required this.child,
this.isLessOptimization = false,
});
@override
Widget build(BuildContext context) {
Widget _buildWithBackgroundImage(
BuildContext context,
File imageFile,
Widget child,
) {
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
return ScaffoldMessenger(
child: FutureBuilder(
future:
kIsWeb ? Future.value(null) : getApplicationDocumentsDirectory(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final path = '${snapshot.data!.path}/app_background_image';
final file = File(path);
if (file.existsSync()) {
if (isLessOptimization) {
final size = MediaQuery.of(context).size;
return Container(
color: Theme.of(context).colorScheme.surface,
child: Container(
decoration: BoxDecoration(
backgroundBlendMode: BlendMode.darken,
color: Theme.of(context).colorScheme.surface,
image: DecorationImage(
opacity: 0.2,
image: ResizeImage(
FileImage(imageFile),
width: (size.width * devicePixelRatio).round(),
height: (size.height * devicePixelRatio).round(),
policy: ResizeImagePolicy.fit,
),
fit: BoxFit.cover,
),
),
child: child,
),
);
}
return Container(
color: Theme.of(context).colorScheme.surface,
child: LayoutBuilder(
@@ -32,11 +55,9 @@ class AppBackground extends StatelessWidget {
image: DecorationImage(
opacity: 0.2,
image: ResizeImage(
FileImage(file),
width: (constraints.maxWidth * devicePixelRatio)
.round(),
height: (constraints.maxHeight * devicePixelRatio)
.round(),
FileImage(imageFile),
width: (constraints.maxWidth * devicePixelRatio).round(),
height: (constraints.maxHeight * devicePixelRatio).round(),
policy: ResizeImagePolicy.fit,
),
fit: BoxFit.cover,
@@ -48,6 +69,20 @@ class AppBackground extends StatelessWidget {
),
);
}
@override
Widget build(BuildContext context) {
return ScaffoldMessenger(
child: FutureBuilder(
future:
kIsWeb ? Future.value(null) : getApplicationDocumentsDirectory(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final path = '${snapshot.data!.path}/app_background_image';
final file = File(path);
if (file.existsSync()) {
return _buildWithBackgroundImage(context, file, child);
}
}
return Material(

View File

@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:surface/providers/navigation.dart';
import 'package:surface/widgets/connection_indicator.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_background.dart';
@@ -9,12 +10,12 @@ import 'package:surface/widgets/navigation/app_bottom_navigation.dart';
import 'package:surface/widgets/navigation/app_drawer_navigation.dart';
import 'package:surface/widgets/navigation/app_rail_navigation.dart';
class AppScaffold extends StatelessWidget {
class AppPageScaffold extends StatelessWidget {
final String? title;
final Widget? body;
final bool showAppBar;
final bool showBottomNavigation;
const AppScaffold({
const AppPageScaffold({
super.key,
this.title,
this.body,
@@ -24,14 +25,12 @@ class AppScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isShowBottomNavigation = (showBottomNavigation)
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
: false;
final state = GoRouter.maybeOf(context);
final autoTitle = state != null
? 'screen${state.routerDelegate.currentConfiguration.last.route.name?.capitalize()}'
: 'screen';
final routeName =
state?.routerDelegate.currentConfiguration.last.route.name;
final autoTitle =
state != null ? 'screen${routeName?.capitalize()}' : 'screen';
return Scaffold(
appBar: showAppBar
@@ -40,8 +39,6 @@ class AppScaffold extends StatelessWidget {
)
: null,
body: body,
bottomNavigationBar:
isShowBottomNavigation ? AppBottomNavigationBar() : null,
);
}
}
@@ -58,6 +55,17 @@ class AppRootScaffold extends StatelessWidget {
ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE);
final isExpandDrawer = ResponsiveBreakpoints.of(context).largerThan(TABLET);
final routeName = GoRouter.of(context)
.routerDelegate
.currentConfiguration
.last
.route
.name;
final isShowBottomNavigation =
NavigationProvider.kShowBottomNavScreen.contains(routeName)
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
: false;
final innerWidget = isCollapseDrawer
? body
: Row(
@@ -88,6 +96,8 @@ class AppRootScaffold extends StatelessWidget {
],
),
drawer: !isExpandDrawer ? AppNavigationDrawer() : null,
bottomNavigationBar:
isShowBottomNavigation ? AppBottomNavigationBar() : null,
),
);
}

View File

@@ -658,6 +658,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hive:
dependency: "direct main"
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
hive_flutter:
dependency: "direct main"
description:
name: hive_flutter
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
url: "https://pub.dev"
source: hosted
version: "1.1.0"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
html:
dependency: transitive
description:

View File

@@ -74,6 +74,8 @@ dependencies:
collection: ^1.18.0
mime: ^2.0.0
web_socket_channel: ^3.0.1
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
flutter_test:
@@ -90,6 +92,7 @@ dev_dependencies:
json_serializable: ^6.8.0
icons_launcher: ^3.0.0
flutter_native_splash: ^2.4.2
hive_generator: ^2.0.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec