Compare commits
11 Commits
2.0.0+4
...
5032cccf38
| Author | SHA1 | Date | |
|---|---|---|---|
| 5032cccf38 | |||
| 9f7a3082cb | |||
| 359cd94532 | |||
| 432705c570 | |||
| 2065350698 | |||
| 285bb42b09 | |||
| e9fbd0c65f | |||
| 835203706d | |||
| 0e208cc320 | |||
| ee2cb0c989 | |||
| 37c61a0406 |
@@ -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,24 @@
|
||||
"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!",
|
||||
"fieldChatMessage": "Message in {}"
|
||||
}
|
||||
|
||||
@@ -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,24 @@
|
||||
"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": "你确定要删除这个领域吗?该操作不可撤销,其隶属于该领域的所有资源(帖子、聊天频道、发布者、制品等)都将被永久删除。三思而后行!",
|
||||
"fieldChatMessage": "在 {} 中发消息"
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ PODS:
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- isar_flutter_libs (1.0.0):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -72,6 +74,7 @@ DEPENDENCIES:
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
@@ -101,6 +104,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
isar_flutter_libs:
|
||||
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
shared_preferences_foundation:
|
||||
@@ -121,6 +126,7 @@ SPEC CHECKSUMS:
|
||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
|
||||
408
lib/controllers/chat_message_controller.dart
Normal file
408
lib/controllers/chat_message_controller.dart
Normal file
@@ -0,0 +1,408 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/providers/websocket.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class ChatMessageController extends ChangeNotifier {
|
||||
static const kChatMessageBoxPrefix = 'nex_chat_messages_';
|
||||
static const kSingleBatchLoadLimit = 100;
|
||||
|
||||
late final SnNetworkProvider _sn;
|
||||
late final UserDirectoryProvider _ud;
|
||||
late final WebSocketProvider _ws;
|
||||
late final SnAttachmentProvider _attach;
|
||||
|
||||
StreamSubscription? _wsSubscription;
|
||||
|
||||
ChatMessageController(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_ud = context.read<UserDirectoryProvider>();
|
||||
_ws = context.read<WebSocketProvider>();
|
||||
_attach = context.read<SnAttachmentProvider>();
|
||||
}
|
||||
|
||||
bool isPending = true;
|
||||
bool isLoading = false;
|
||||
|
||||
int? messageTotal;
|
||||
|
||||
bool get isAllLoaded =>
|
||||
messageTotal != null && messages.length >= messageTotal!;
|
||||
|
||||
String? _boxKey;
|
||||
SnChannel? channel;
|
||||
SnChannelMember? profile;
|
||||
|
||||
/// Messages are the all the messages that in the channel
|
||||
final List<SnChatMessage> messages = List.empty(growable: true);
|
||||
|
||||
/// Unconfirmed messages are the messages that sent by client but did not receive the reply from websocket server.
|
||||
/// Stored as a list of nonce to provide the loading state
|
||||
final List<String> unconfirmedMessages = List.empty(growable: true);
|
||||
|
||||
Box<SnChatMessage>? get _box =>
|
||||
(_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!);
|
||||
|
||||
Future<void> initialize(SnChannel chan) async {
|
||||
channel = chan;
|
||||
|
||||
// Initialize local data
|
||||
_boxKey = '$kChatMessageBoxPrefix${chan.id}';
|
||||
await Hive.openBox<SnChatMessage>(_boxKey!);
|
||||
|
||||
// Fetch channel profile
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/${chan.keyPath}/me',
|
||||
);
|
||||
profile = SnChannelMember.fromJson(
|
||||
resp.data as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
_wsSubscription = _ws.stream.stream.listen((event) {
|
||||
switch (event.method) {
|
||||
case 'events.new':
|
||||
final payload = SnChatMessage.fromJson(event.payload!);
|
||||
_addMessage(payload);
|
||||
break;
|
||||
case 'calls.new':
|
||||
final payload = SnChatMessage.fromJson(event.payload!);
|
||||
if (payload.channel.id == channel?.id) {
|
||||
// TODO impl call
|
||||
}
|
||||
break;
|
||||
case 'calls.end':
|
||||
final payload = SnChatMessage.fromJson(event.payload!);
|
||||
if (payload.channel.id == channel?.id) {
|
||||
// TODO impl call
|
||||
}
|
||||
break;
|
||||
case 'status.typing':
|
||||
if (event.payload?['channel_id'] != channel?.id) break;
|
||||
final member = SnChannelMember.fromJson(event.payload!['member']);
|
||||
if (member.id == profile?.id) break;
|
||||
// TODO impl typing users
|
||||
// if (!_typingUsers.any((x) => x.id == member.id)) {
|
||||
// setState(() {
|
||||
// _typingUsers.add(member);
|
||||
// });
|
||||
// }
|
||||
// _typingInactiveTimer[member.id]?.cancel();
|
||||
// _typingInactiveTimer[member.id] = Timer(
|
||||
// const Duration(seconds: 3),
|
||||
// () {
|
||||
// setState(() {
|
||||
// _typingUsers.removeWhere((x) => x.id == member.id);
|
||||
// _typingInactiveTimer.remove(member.id);
|
||||
// });
|
||||
// },
|
||||
// );
|
||||
}
|
||||
});
|
||||
|
||||
isPending = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _saveMessageToLocal(Iterable<SnChatMessage> messages) async {
|
||||
if (_box == null) return;
|
||||
await _box!.putAll({
|
||||
for (final message in messages) message.id: message,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _addUnconfirmedMessage(SnChatMessage message) async {
|
||||
SnChatMessage? quoteEvent;
|
||||
if (message.body['quote_event'] != null) {
|
||||
quoteEvent = await getMessage(message.body['quote_event'] as int);
|
||||
}
|
||||
|
||||
final attachmentRid = List<String>.from(
|
||||
message.body['attachments']?.cast<String>() ?? [],
|
||||
);
|
||||
final attachments = await _attach.getMultiple(attachmentRid);
|
||||
message = message.copyWith(
|
||||
preload: SnChatMessagePreload(
|
||||
quoteEvent: quoteEvent,
|
||||
attachments: attachments,
|
||||
),
|
||||
);
|
||||
|
||||
messages.insert(0, message);
|
||||
unconfirmedMessages.add(message.uuid);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _addMessage(SnChatMessage message) async {
|
||||
SnChatMessage? quoteEvent;
|
||||
if (message.body['quote_event'] != null) {
|
||||
quoteEvent = await getMessage(message.body['quote_event'] as int);
|
||||
}
|
||||
|
||||
final attachmentRid = List<String>.from(
|
||||
message.body['attachments']?.cast<String>() ?? [],
|
||||
);
|
||||
final attachments = await _attach.getMultiple(attachmentRid);
|
||||
message = message.copyWith(
|
||||
preload: SnChatMessagePreload(
|
||||
quoteEvent: quoteEvent,
|
||||
attachments: attachments,
|
||||
),
|
||||
);
|
||||
|
||||
final idx = messages.indexWhere((e) => e.uuid == message.uuid);
|
||||
if (idx != -1) {
|
||||
unconfirmedMessages.remove(message.uuid);
|
||||
messages[idx] = message;
|
||||
} else {
|
||||
messages.insert(0, message);
|
||||
}
|
||||
await _applyMessage(message);
|
||||
notifyListeners();
|
||||
|
||||
if (_box == null) return;
|
||||
await _box!.put(message.id, message);
|
||||
}
|
||||
|
||||
Future<void> _applyMessage(SnChatMessage message) async {
|
||||
if (message.channelId != channel?.id) return;
|
||||
|
||||
switch (message.type) {
|
||||
case 'messages.edit':
|
||||
final body = message.body;
|
||||
if (body['related_event'] != null) {
|
||||
final idx = messages.indexWhere((x) => x.id == body['related_event']);
|
||||
if (idx != -1) {
|
||||
final newBody = message.body;
|
||||
newBody.remove('related_event');
|
||||
messages[idx] = messages[idx].copyWith(
|
||||
body: newBody,
|
||||
updatedAt: message.updatedAt,
|
||||
);
|
||||
if (_box!.containsKey(body['related_event'])) {
|
||||
await _box!.put(body['related_event'], messages[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'messages.delete':
|
||||
final body = message.body;
|
||||
if (body['related_event'] != null) {
|
||||
messages.removeWhere((x) => x.id == body['related_event']);
|
||||
if (_box!.containsKey(body['related_event'])) {
|
||||
await _box!.delete(body['related_event']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendMessage(
|
||||
String type,
|
||||
String content, {
|
||||
int? quoteId,
|
||||
int? relatedId,
|
||||
List<String>? attachments,
|
||||
}) async {
|
||||
if (channel == null) return;
|
||||
const uuid = Uuid();
|
||||
final nonce = uuid.v4();
|
||||
final body = {
|
||||
'text': content,
|
||||
'algorithm': 'plain',
|
||||
if (quoteId != null) 'quote_event': quoteId,
|
||||
if (relatedId != null) 'quote_event': relatedId,
|
||||
if (attachments != null && attachments.isNotEmpty)
|
||||
'attachments': attachments,
|
||||
};
|
||||
|
||||
// Mock the message locally
|
||||
final message = SnChatMessage(
|
||||
id: 0,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
deletedAt: null,
|
||||
uuid: nonce,
|
||||
body: body,
|
||||
type: type,
|
||||
channel: channel!,
|
||||
channelId: channel!.id,
|
||||
sender: profile!,
|
||||
senderId: profile!.id,
|
||||
);
|
||||
_addUnconfirmedMessage(message);
|
||||
|
||||
// Send to server
|
||||
try {
|
||||
await _sn.client.post(
|
||||
'/cgi/im/channels/${channel!.keyPath}/messages',
|
||||
data: {
|
||||
'type': type,
|
||||
'uuid': nonce,
|
||||
'body': body,
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check the local storage is up to date with the server.
|
||||
/// If the local storage is not up to date, it will be updated.
|
||||
Future<void> checkUpdate() async {
|
||||
if (_box == null) return;
|
||||
if (_box!.isEmpty) return;
|
||||
|
||||
isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/${channel!.keyPath}/events/update',
|
||||
queryParameters: {
|
||||
'pivot': _box!.values.last.id,
|
||||
},
|
||||
);
|
||||
if (resp.data['up_to_date'] == true) return;
|
||||
// Only preload the first 100 messages to prevent first time check update cause load to server and waste local storage.
|
||||
// FIXME If the local is missing more than 100 messages, it won't be fetched, this is a problem, we need to fix it.
|
||||
final countToFetch = math.min(resp.data['count'] as int, 100);
|
||||
|
||||
for (int idx = 0; idx < countToFetch; idx += kSingleBatchLoadLimit) {
|
||||
await getMessages(kSingleBatchLoadLimit, idx);
|
||||
}
|
||||
} catch (err) {
|
||||
rethrow;
|
||||
} finally {
|
||||
await loadMessages();
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a single event from the current channel
|
||||
/// If it was not found in local storage we will look it up in remote
|
||||
Future<SnChatMessage?> getMessage(int id) async {
|
||||
SnChatMessage? out;
|
||||
if (_box != null && _box!.containsKey(id)) {
|
||||
out = _box!.get(id);
|
||||
}
|
||||
|
||||
if (out == null) {
|
||||
try {
|
||||
final resp = await _sn.client
|
||||
.get('/cgi/im/channels/${channel!.keyPath}/events/$id');
|
||||
out = SnChatMessage.fromJson(resp.data);
|
||||
_saveMessageToLocal([out]);
|
||||
} catch (_) {
|
||||
// ignore, maybe not found
|
||||
}
|
||||
}
|
||||
|
||||
// Preload some related things if found
|
||||
if (out != null) {
|
||||
await _ud.listAccount([out.sender.accountId]);
|
||||
|
||||
final attachments = await _attach.getMultiple(
|
||||
out.body['attachments']?.cast<String>() ?? [],
|
||||
);
|
||||
out = out.copyWith(
|
||||
preload: SnChatMessagePreload(
|
||||
attachments: attachments,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Get message from local storage first, then from the server.
|
||||
/// Will not check local storage is up to date with the server.
|
||||
/// If you need to do the sync, do the `checkUpdate` instead.
|
||||
Future<List<SnChatMessage>> getMessages(
|
||||
int take,
|
||||
int offset, {
|
||||
bool forceLocal = false,
|
||||
}) async {
|
||||
late List<SnChatMessage> out;
|
||||
if (_box != null && (_box!.length >= take + offset || forceLocal)) {
|
||||
out = _box!.values.skip(offset).take(take).toList().reversed.toList();
|
||||
} else {
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/${channel!.keyPath}/events',
|
||||
queryParameters: {
|
||||
'take': take,
|
||||
'offset': offset,
|
||||
},
|
||||
);
|
||||
messageTotal = resp.data['count'] as int?;
|
||||
out = List<SnChatMessage>.from(
|
||||
resp.data['data']?.map((e) => SnChatMessage.fromJson(e)) ?? [],
|
||||
);
|
||||
_saveMessageToLocal(out);
|
||||
}
|
||||
|
||||
// Preload attachments
|
||||
final attachmentRid = List<String>.from(
|
||||
out.expand((e) => (e.body['attachments'] as List<dynamic>?) ?? []),
|
||||
);
|
||||
final attachments = await _attach.getMultiple(attachmentRid);
|
||||
|
||||
// Putting preload back to data
|
||||
for (var i = 0; i < out.length; i++) {
|
||||
// Preload related events (quoted)
|
||||
SnChatMessage? quoteEvent;
|
||||
if (out[i].body['quote_event'] != null) {
|
||||
quoteEvent = await getMessage(out[i].body['quote_event'] as int);
|
||||
}
|
||||
|
||||
out[i] = out[i].copyWith(
|
||||
preload: SnChatMessagePreload(
|
||||
quoteEvent: quoteEvent,
|
||||
attachments: attachments
|
||||
.where(
|
||||
(ele) =>
|
||||
out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Preload sender accounts
|
||||
await _ud.listAccount(out.map((ele) => ele.sender.accountId).toSet());
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/// The load messages method work as same as the `getMessages` method.
|
||||
/// But it won't return the messages instead append them to the value that controller has.
|
||||
/// At the same time, this method provide the `isLoading` state.
|
||||
/// The `skip` parameter is no longer required since it will skip the messages count that already loaded.
|
||||
Future<void> loadMessages({int take = 20}) async {
|
||||
isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final out = await getMessages(take, messages.length);
|
||||
messages.addAll(out);
|
||||
} catch (err) {
|
||||
rethrow;
|
||||
} finally {
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_box?.close();
|
||||
_wsSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,32 @@ 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';
|
||||
import 'package:surface/providers/channel.dart';
|
||||
import 'package:surface/providers/navigation.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/theme.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/providers/websocket.dart';
|
||||
import 'package:surface/router.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
|
||||
await Hive.initFlutter();
|
||||
Hive.registerAdapter(SnChannelImplAdapter());
|
||||
Hive.registerAdapter(SnRealmImplAdapter());
|
||||
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
||||
Hive.registerAdapter(SnChatMessageImplAdapter());
|
||||
|
||||
if (!kReleaseMode) {
|
||||
debugInvertOversizedImages = true;
|
||||
}
|
||||
@@ -47,8 +58,10 @@ class SolianApp extends StatelessWidget {
|
||||
// Data layer
|
||||
Provider(create: (_) => SnNetworkProvider()),
|
||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)),
|
||||
],
|
||||
child: AppMainContent(),
|
||||
),
|
||||
@@ -69,6 +82,7 @@ class AppMainContent extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
context.read<NavigationProvider>();
|
||||
context.read<WebSocketProvider>();
|
||||
context.read<ChatChannelProvider>();
|
||||
|
||||
final th = context.watch<ThemeProvider>();
|
||||
|
||||
|
||||
121
lib/providers/channel.dart
Normal file
121
lib/providers/channel.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
class ChatChannelProvider extends ChangeNotifier {
|
||||
static const kChatChannelBoxName = 'nex_chat_channels';
|
||||
|
||||
late final SnNetworkProvider _sn;
|
||||
|
||||
Box<SnChannel>? get _channelBox => Hive.box<SnChannel>(kChatChannelBoxName);
|
||||
|
||||
ChatChannelProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_initializeLocalData();
|
||||
}
|
||||
|
||||
Future<void> _initializeLocalData() async {
|
||||
await Hive.openBox<SnChannel>(kChatChannelBoxName);
|
||||
}
|
||||
|
||||
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
||||
if (_channelBox == null) return;
|
||||
await _channelBox!.putAll({
|
||||
for (final channel in channels) channel.key: channel,
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<SnChannel>> _fetchChannelsFromServer({
|
||||
String scope = 'global',
|
||||
bool direct = false,
|
||||
bool doNotSave = false,
|
||||
}) async {
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/im/channels/$scope/me/available',
|
||||
queryParameters: {
|
||||
'direct': direct,
|
||||
},
|
||||
);
|
||||
final out = List<SnChannel>.from(
|
||||
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
|
||||
);
|
||||
if (!doNotSave) _saveChannelToLocal(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// The get channel method will return the channel with the given alias.
|
||||
/// It will use the local storage as much as possible.
|
||||
/// The alias should include the scope, formatted as `scope:alias`.
|
||||
Future<SnChannel> getChannel(String key) async {
|
||||
if (_channelBox != null) {
|
||||
final local = _channelBox!.get(key);
|
||||
if (local != null) return local;
|
||||
}
|
||||
|
||||
var resp = await _sn.client.get('/cgi/im/channels/$key');
|
||||
var out = SnChannel.fromJson(resp.data);
|
||||
|
||||
// Preload realm of the channel
|
||||
if (out.realmId != null) {
|
||||
resp = await _sn.client.get('/cgi/id/realms/${out.realmId}');
|
||||
out = out.copyWith(realm: SnRealm.fromJson(resp.data));
|
||||
}
|
||||
|
||||
_saveChannelToLocal([out]);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// The fetch channel method return a stream, which will emit twice.
|
||||
/// The first time is when the data was fetched from the local storage.
|
||||
/// And the second time is when the data was fetched from the server.
|
||||
/// But there is some exception that will only cause one of them to be emitted.
|
||||
/// Like the local storage is broken or the server is down.
|
||||
Stream<List<SnChannel>> fetchChannels() async* {
|
||||
if (_channelBox != null) yield _channelBox!.values.toList();
|
||||
|
||||
var resp = await _sn.client.get('/cgi/id/realms/me/available');
|
||||
final realms = List<SnRealm>.from(
|
||||
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||
);
|
||||
final realmMap = {
|
||||
for (final realm in realms) realm.alias: realm,
|
||||
};
|
||||
|
||||
final scopeToFetch = {'global', ...realms.map((e) => e.alias)};
|
||||
|
||||
final List<SnChannel> result = List.empty(growable: true);
|
||||
final directMessages = await _fetchChannelsFromServer(
|
||||
scope: scopeToFetch.first,
|
||||
direct: true,
|
||||
);
|
||||
result.addAll(directMessages);
|
||||
|
||||
final nonBelongsChannels = await _fetchChannelsFromServer(
|
||||
scope: scopeToFetch.first,
|
||||
direct: false,
|
||||
);
|
||||
result.addAll(nonBelongsChannels);
|
||||
|
||||
for (final scope in scopeToFetch.skip(1)) {
|
||||
final channel = await _fetchChannelsFromServer(
|
||||
scope: scope,
|
||||
direct: false,
|
||||
doNotSave: true,
|
||||
);
|
||||
final out = channel.map((ele) => ele.copyWith(realm: realmMap[scope]));
|
||||
_saveChannelToLocal(out);
|
||||
result.addAll(out);
|
||||
}
|
||||
|
||||
yield result;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_channelBox?.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -19,6 +19,14 @@ class SnAttachmentProvider {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
}
|
||||
|
||||
void putCache(Iterable<SnAttachment> items, {bool noCheck = false}) {
|
||||
for (final item in items) {
|
||||
if ((item.isAnalyzed && item.isUploaded) || noCheck) {
|
||||
_cache[item.rid] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<SnAttachment> getOne(String rid, {noCache = false}) async {
|
||||
if (!noCache && _cache.containsKey(rid)) {
|
||||
return _cache[rid]!;
|
||||
@@ -26,37 +34,48 @@ class SnAttachmentProvider {
|
||||
|
||||
final resp = await _sn.client.get('/cgi/uc/attachments/$rid/meta');
|
||||
final out = SnAttachment.fromJson(resp.data);
|
||||
_cache[rid] = out;
|
||||
if (out.isAnalyzed && out.isUploaded) {
|
||||
_cache[rid] = out;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Future<List<SnAttachment>> getMultiple(List<String> rids,
|
||||
Future<List<SnAttachment?>> getMultiple(List<String> rids,
|
||||
{noCache = false}) async {
|
||||
final pendingFetch =
|
||||
noCache ? rids : rids.where((rid) => !_cache.containsKey(rid)).toList();
|
||||
final result = List<SnAttachment?>.filled(rids.length, null);
|
||||
final Map<String, int> randomMapping = {};
|
||||
for (int i = 0; i < rids.length; i++) {
|
||||
final rid = rids[i];
|
||||
if (noCache || !_cache.containsKey(rid)) {
|
||||
randomMapping[rid] = i;
|
||||
} else {
|
||||
result[i] = _cache[rid]!;
|
||||
}
|
||||
}
|
||||
final pendingFetch = randomMapping.keys;
|
||||
|
||||
if (pendingFetch.isEmpty) {
|
||||
return rids.map((rid) => _cache[rid]!).toList();
|
||||
if (pendingFetch.isNotEmpty) {
|
||||
final resp = await _sn.client.get(
|
||||
'/cgi/uc/attachments',
|
||||
queryParameters: {
|
||||
'take': pendingFetch.length,
|
||||
'id': pendingFetch.join(','),
|
||||
},
|
||||
);
|
||||
final out = resp.data['data']
|
||||
.map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e))
|
||||
.toList();
|
||||
|
||||
for (final item in out) {
|
||||
if (item.isAnalyzed && item.isUploaded) {
|
||||
_cache[item.rid] = item;
|
||||
}
|
||||
result[randomMapping[item.rid]!] = item;
|
||||
}
|
||||
}
|
||||
|
||||
final resp = await _sn.client.get('/cgi/uc/attachments', queryParameters: {
|
||||
'take': pendingFetch.length,
|
||||
'id': pendingFetch.join(','),
|
||||
});
|
||||
final out = resp.data['data']
|
||||
.where((e) => e['id'] != 0)
|
||||
.map((e) => SnAttachment.fromJson(e))
|
||||
.toList();
|
||||
|
||||
for (final item in out) {
|
||||
_cache[item.rid] = item;
|
||||
}
|
||||
|
||||
return rids
|
||||
.where((rid) => _cache.containsKey(rid))
|
||||
.map((rid) => _cache[rid]!)
|
||||
.toList();
|
||||
return result;
|
||||
}
|
||||
|
||||
static Map<String, String> mimetypeOverrides = {
|
||||
|
||||
@@ -28,15 +28,15 @@ class SnNetworkProvider {
|
||||
SnNetworkProvider() {
|
||||
client = Dio();
|
||||
|
||||
client.interceptors.add(RetryInterceptor(
|
||||
dio: client,
|
||||
retries: 3,
|
||||
retryDelays: const [
|
||||
Duration(milliseconds: 300),
|
||||
Duration(milliseconds: 1000),
|
||||
Duration(milliseconds: 3000),
|
||||
],
|
||||
));
|
||||
// client.interceptors.add(RetryInterceptor(
|
||||
// dio: client,
|
||||
// retries: 3,
|
||||
// retryDelays: const [
|
||||
// Duration(milliseconds: 300),
|
||||
// Duration(milliseconds: 1000),
|
||||
// Duration(milliseconds: 3000),
|
||||
// ],
|
||||
// ));
|
||||
|
||||
client.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
|
||||
50
lib/providers/user_directory.dart
Normal file
50
lib/providers/user_directory.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
|
||||
class UserDirectoryProvider {
|
||||
late final SnNetworkProvider _sn;
|
||||
|
||||
UserDirectoryProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
}
|
||||
|
||||
final Map<String, int> _idCache = {};
|
||||
final Map<int, SnAccount> _cache = {};
|
||||
|
||||
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
|
||||
final out = await Future.wait(
|
||||
id.map((e) => getAccount(e)),
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
Future<SnAccount?> getAccount(dynamic id) async {
|
||||
if (id is String && _idCache.containsKey(id)) {
|
||||
id = _idCache[id];
|
||||
}
|
||||
if (_cache.containsKey(id)) {
|
||||
return _cache[id];
|
||||
}
|
||||
|
||||
try {
|
||||
final resp = await _sn.client.get('/cgi/id/users/$id');
|
||||
final account = SnAccount.fromJson(
|
||||
resp.data as Map<String, dynamic>,
|
||||
);
|
||||
_cache[account.id] = account;
|
||||
if (id is String) _idCache[id] = account.id;
|
||||
return account;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
SnAccount? getAccountFromCache(dynamic id) {
|
||||
if (id is String && _idCache.containsKey(id)) {
|
||||
id = _idCache[id];
|
||||
}
|
||||
return _cache[id];
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ class WebSocketProvider extends ChangeNotifier {
|
||||
try {
|
||||
conn = WebSocketChannel.connect(uri);
|
||||
await conn!.ready;
|
||||
listen();
|
||||
log('[WebSocket] Connected to server!');
|
||||
isConnected = true;
|
||||
} catch (err) {
|
||||
|
||||
170
lib/router.dart
170
lib/router.dart
@@ -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,80 +9,171 @@ 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/chat/room.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(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/post/write/:mode',
|
||||
name: 'postEditor',
|
||||
pageBuilder: (context, state) => CustomTransitionPage(
|
||||
child: PostEditorScreen(
|
||||
mode: state.pathParameters['mode']!,
|
||||
postEditId: int.tryParse(
|
||||
state.uri.queryParameters['editing'] ?? '',
|
||||
),
|
||||
postReplyId: int.tryParse(
|
||||
state.uri.queryParameters['replying'] ?? '',
|
||||
),
|
||||
postRepostId: int.tryParse(
|
||||
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',
|
||||
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',
|
||||
builder: (context, state) => const AccountScreen(),
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: const AccountScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat',
|
||||
name: 'chat',
|
||||
builder: (context, state) => const ChatScreen(),
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: const ChatScreen(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/chat/:scope/:alias',
|
||||
name: 'chatRoom',
|
||||
builder: (context, state) => AppBackground(
|
||||
isLessOptimization: true,
|
||||
child: ChatRoomScreen(
|
||||
scope: state.pathParameters['scope']!,
|
||||
alias: state.pathParameters['alias']!,
|
||||
),
|
||||
),
|
||||
),
|
||||
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',
|
||||
builder: (context, state) => const AlbumScreen(),
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: const AlbumScreen(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => child,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/post/write/:mode',
|
||||
name: 'postEditor',
|
||||
builder: (context, state) => PostEditorScreen(
|
||||
mode: state.pathParameters['mode']!,
|
||||
postEditId: int.tryParse(
|
||||
state.uri.queryParameters['editing'] ?? '',
|
||||
),
|
||||
postReplyId: int.tryParse(
|
||||
state.uri.queryParameters['replying'] ?? '',
|
||||
),
|
||||
postRepostId: int.tryParse(
|
||||
state.uri.queryParameters['reposting'] ?? '',
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/post/:slug',
|
||||
name: 'postDetail',
|
||||
builder: (context, state) => PostDetailScreen(
|
||||
slug: state.pathParameters['slug']!,
|
||||
preload: state.extra as SnPost?,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => AppScaffold(body: child),
|
||||
builder: (context, state, child) => AppPageScaffold(body: child),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/auth/login',
|
||||
@@ -118,7 +210,7 @@ final _appRoutes = [
|
||||
],
|
||||
),
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => AppScaffold(body: child),
|
||||
builder: (context, state, child) => AppPageScaffold(body: child),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
@@ -132,8 +224,8 @@ final _appRoutes = [
|
||||
final appRouter = GoRouter(
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => AppRootScaffold(body: child),
|
||||
routes: _appRoutes,
|
||||
builder: (context, state, child) => AppRootScaffold(body: child),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,91 @@
|
||||
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/channel.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 = true;
|
||||
|
||||
List<SnChannel>? _channels;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final chan = context.read<ChatChannelProvider>();
|
||||
chan.fetchChannels().listen((channels) {
|
||||
if (mounted) setState(() => _channels = channels);
|
||||
})
|
||||
..onError((err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
setState(() => _isBusy = false);
|
||||
})
|
||||
..onDone(() {
|
||||
if (!mounted) return;
|
||||
setState(() => _isBusy = false);
|
||||
});
|
||||
}
|
||||
|
||||
@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,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
leading: AccountImage(
|
||||
content: null,
|
||||
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'chatRoom',
|
||||
pathParameters: {
|
||||
'scope': channel.realm?.alias ?? 'global',
|
||||
'alias': channel.alias,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
292
lib/screens/chat/manage.dart
Normal file
292
lib/screens/chat/manage.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
142
lib/screens/chat/room.dart
Normal file
142
lib/screens/chat/room.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/controllers/chat_message_controller.dart';
|
||||
import 'package:surface/providers/channel.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/chat/chat_message.dart';
|
||||
import 'package:surface/widgets/chat/chat_message_input.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/loading_indicator.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
|
||||
class ChatRoomScreen extends StatefulWidget {
|
||||
final String scope;
|
||||
final String alias;
|
||||
const ChatRoomScreen({super.key, required this.scope, required this.alias});
|
||||
|
||||
@override
|
||||
State<ChatRoomScreen> createState() => _ChatRoomScreenState();
|
||||
}
|
||||
|
||||
class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
bool _isBusy = false;
|
||||
|
||||
SnChannel? _channel;
|
||||
|
||||
final GlobalKey<ChatMessageInputState> _inputGlobalKey = GlobalKey();
|
||||
late final ChatMessageController _messageController;
|
||||
|
||||
Future<void> _fetchChannel() async {
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final chan = context.read<ChatChannelProvider>();
|
||||
_channel = await chan.getChannel('${widget.scope}:${widget.alias}');
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_messageController = ChatMessageController(context);
|
||||
_fetchChannel().then((_) async {
|
||||
await _messageController.initialize(_channel!);
|
||||
await _messageController.checkUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_channel?.name ?? 'loading'.tr()),
|
||||
),
|
||||
body: ListenableBuilder(
|
||||
listenable: _messageController,
|
||||
builder: (context, _) {
|
||||
return Column(
|
||||
children: [
|
||||
LoadingIndicator(isActive: _isBusy),
|
||||
if (_messageController.isPending)
|
||||
Expanded(
|
||||
child: const CircularProgressIndicator().center(),
|
||||
),
|
||||
if (!_messageController.isPending)
|
||||
Expanded(
|
||||
child: InfiniteList(
|
||||
reverse: true,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 12,
|
||||
),
|
||||
hasReachedMax: _messageController.isAllLoaded,
|
||||
itemCount: _messageController.messages.length,
|
||||
isLoading: _messageController.isLoading,
|
||||
onFetchData: () {
|
||||
_messageController.loadMessages();
|
||||
},
|
||||
itemBuilder: (context, idx) {
|
||||
final message = _messageController.messages[idx];
|
||||
final nextMessage =
|
||||
idx < _messageController.messages.length - 1
|
||||
? _messageController.messages[idx + 1]
|
||||
: null;
|
||||
final previousMessage =
|
||||
idx > 0 ? _messageController.messages[idx - 1] : null;
|
||||
|
||||
final canMerge = nextMessage != null &&
|
||||
nextMessage.senderId == message.senderId &&
|
||||
message.createdAt
|
||||
.difference(nextMessage.createdAt)
|
||||
.inMinutes
|
||||
.abs() <=
|
||||
3;
|
||||
final canMergePrevious = previousMessage != null &&
|
||||
previousMessage.senderId == message.senderId &&
|
||||
message.createdAt
|
||||
.difference(previousMessage.createdAt)
|
||||
.inMinutes
|
||||
.abs() <=
|
||||
3;
|
||||
|
||||
return ChatMessage(
|
||||
data: message,
|
||||
isMerged: canMerge,
|
||||
hasMerged: canMergePrevious,
|
||||
isPending: _messageController.unconfirmedMessages
|
||||
.contains(message.uuid),
|
||||
onReply: () {
|
||||
_inputGlobalKey.currentState?.setReply(message);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (!_messageController.isPending)
|
||||
Material(
|
||||
elevation: 2,
|
||||
child: ChatMessageInput(
|
||||
key: _inputGlobalKey,
|
||||
controller: _messageController,
|
||||
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,8 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
preload: SnPostPreload(
|
||||
attachments: attachments
|
||||
.where(
|
||||
(ele) => out[i].body['attachments']?.contains(ele.rid) ?? false,
|
||||
(ele) =>
|
||||
out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
|
||||
@@ -80,7 +80,6 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
_writeController.addAttachments(
|
||||
result.map((e) => PostWriteMedia.fromFile(e)),
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
220
lib/screens/realm.dart
Normal file
220
lib/screens/realm.dart
Normal 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: () {},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
312
lib/screens/realm/manage.dart
Normal file
312
lib/screens/realm/manage.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,32 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
part 'account.freezed.dart';
|
||||
part 'account.g.dart';
|
||||
|
||||
@freezed
|
||||
class SnAccount with _$SnAccount {
|
||||
const SnAccount._();
|
||||
|
||||
const factory SnAccount({
|
||||
required int id,
|
||||
required int? affiliatedId,
|
||||
required int? affiliatedTo,
|
||||
required int? automatedBy,
|
||||
required int? automatedId,
|
||||
@HiveField(0) required int id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
required DateTime? confirmedAt,
|
||||
required List<SnAccountContact>? contacts,
|
||||
required String avatar,
|
||||
required String banner,
|
||||
required DateTime? confirmedAt,
|
||||
required List<SnAccountContact> contacts,
|
||||
required DateTime createdAt,
|
||||
required DateTime? deletedAt,
|
||||
required String description,
|
||||
required String name,
|
||||
required String nick,
|
||||
required Map<String, dynamic> permNodes,
|
||||
required SnAccountProfile? profile,
|
||||
required DateTime? suspendedAt,
|
||||
required DateTime updatedAt,
|
||||
required int? affiliatedId,
|
||||
required int? affiliatedTo,
|
||||
required int? automatedBy,
|
||||
required int? automatedId,
|
||||
}) = _SnAccount;
|
||||
|
||||
factory SnAccount.fromJson(Map<String, Object?> json) =>
|
||||
|
||||
@@ -20,24 +20,25 @@ SnAccount _$SnAccountFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnAccount {
|
||||
@HiveField(0)
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
int? get affiliatedId => throw _privateConstructorUsedError;
|
||||
int? get affiliatedTo => throw _privateConstructorUsedError;
|
||||
int? get automatedBy => throw _privateConstructorUsedError;
|
||||
int? get automatedId => throw _privateConstructorUsedError;
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||
DateTime? get confirmedAt => throw _privateConstructorUsedError;
|
||||
List<SnAccountContact>? get contacts => throw _privateConstructorUsedError;
|
||||
String get avatar => throw _privateConstructorUsedError;
|
||||
String get banner => throw _privateConstructorUsedError;
|
||||
DateTime? get confirmedAt => throw _privateConstructorUsedError;
|
||||
List<SnAccountContact> get contacts => throw _privateConstructorUsedError;
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||
String get description => throw _privateConstructorUsedError;
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get nick => throw _privateConstructorUsedError;
|
||||
Map<String, dynamic> get permNodes => throw _privateConstructorUsedError;
|
||||
SnAccountProfile? get profile => throw _privateConstructorUsedError;
|
||||
DateTime? get suspendedAt => throw _privateConstructorUsedError;
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
int? get affiliatedId => throw _privateConstructorUsedError;
|
||||
int? get affiliatedTo => throw _privateConstructorUsedError;
|
||||
int? get automatedBy => throw _privateConstructorUsedError;
|
||||
int? get automatedId => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnAccount to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -55,24 +56,24 @@ abstract class $SnAccountCopyWith<$Res> {
|
||||
_$SnAccountCopyWithImpl<$Res, SnAccount>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{int id,
|
||||
int? affiliatedId,
|
||||
int? affiliatedTo,
|
||||
int? automatedBy,
|
||||
int? automatedId,
|
||||
{@HiveField(0) int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
DateTime? confirmedAt,
|
||||
List<SnAccountContact>? contacts,
|
||||
String avatar,
|
||||
String banner,
|
||||
DateTime? confirmedAt,
|
||||
List<SnAccountContact> contacts,
|
||||
DateTime createdAt,
|
||||
DateTime? deletedAt,
|
||||
String description,
|
||||
String name,
|
||||
String nick,
|
||||
Map<String, dynamic> permNodes,
|
||||
SnAccountProfile? profile,
|
||||
DateTime? suspendedAt,
|
||||
DateTime updatedAt});
|
||||
int? affiliatedId,
|
||||
int? affiliatedTo,
|
||||
int? automatedBy,
|
||||
int? automatedId});
|
||||
|
||||
$SnAccountProfileCopyWith<$Res>? get profile;
|
||||
}
|
||||
@@ -93,45 +94,49 @@ class _$SnAccountCopyWithImpl<$Res, $Val extends SnAccount>
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? affiliatedId = freezed,
|
||||
Object? affiliatedTo = freezed,
|
||||
Object? automatedBy = freezed,
|
||||
Object? automatedId = freezed,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? deletedAt = freezed,
|
||||
Object? confirmedAt = freezed,
|
||||
Object? contacts = freezed,
|
||||
Object? avatar = null,
|
||||
Object? banner = null,
|
||||
Object? confirmedAt = freezed,
|
||||
Object? contacts = null,
|
||||
Object? createdAt = null,
|
||||
Object? deletedAt = freezed,
|
||||
Object? description = null,
|
||||
Object? name = null,
|
||||
Object? nick = null,
|
||||
Object? permNodes = null,
|
||||
Object? profile = freezed,
|
||||
Object? suspendedAt = freezed,
|
||||
Object? updatedAt = null,
|
||||
Object? affiliatedId = freezed,
|
||||
Object? affiliatedTo = freezed,
|
||||
Object? automatedBy = freezed,
|
||||
Object? automatedId = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
affiliatedId: freezed == affiliatedId
|
||||
? _value.affiliatedId
|
||||
: affiliatedId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
affiliatedTo: freezed == affiliatedTo
|
||||
? _value.affiliatedTo
|
||||
: affiliatedTo // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedBy: freezed == automatedBy
|
||||
? _value.automatedBy
|
||||
: automatedBy // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedId: freezed == automatedId
|
||||
? _value.automatedId
|
||||
: automatedId // 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?,
|
||||
confirmedAt: freezed == confirmedAt
|
||||
? _value.confirmedAt
|
||||
: confirmedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
contacts: freezed == contacts
|
||||
? _value.contacts
|
||||
: contacts // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAccountContact>?,
|
||||
avatar: null == avatar
|
||||
? _value.avatar
|
||||
: avatar // ignore: cast_nullable_to_non_nullable
|
||||
@@ -140,22 +145,6 @@ class _$SnAccountCopyWithImpl<$Res, $Val extends SnAccount>
|
||||
? _value.banner
|
||||
: banner // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
confirmedAt: freezed == confirmedAt
|
||||
? _value.confirmedAt
|
||||
: confirmedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
contacts: null == contacts
|
||||
? _value.contacts
|
||||
: contacts // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAccountContact>,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
deletedAt: freezed == deletedAt
|
||||
? _value.deletedAt
|
||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
description: null == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
@@ -180,10 +169,22 @@ class _$SnAccountCopyWithImpl<$Res, $Val extends SnAccount>
|
||||
? _value.suspendedAt
|
||||
: suspendedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
affiliatedId: freezed == affiliatedId
|
||||
? _value.affiliatedId
|
||||
: affiliatedId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
affiliatedTo: freezed == affiliatedTo
|
||||
? _value.affiliatedTo
|
||||
: affiliatedTo // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedBy: freezed == automatedBy
|
||||
? _value.automatedBy
|
||||
: automatedBy // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedId: freezed == automatedId
|
||||
? _value.automatedId
|
||||
: automatedId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@@ -211,24 +212,24 @@ abstract class _$$SnAccountImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{int id,
|
||||
int? affiliatedId,
|
||||
int? affiliatedTo,
|
||||
int? automatedBy,
|
||||
int? automatedId,
|
||||
{@HiveField(0) int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
DateTime? confirmedAt,
|
||||
List<SnAccountContact>? contacts,
|
||||
String avatar,
|
||||
String banner,
|
||||
DateTime? confirmedAt,
|
||||
List<SnAccountContact> contacts,
|
||||
DateTime createdAt,
|
||||
DateTime? deletedAt,
|
||||
String description,
|
||||
String name,
|
||||
String nick,
|
||||
Map<String, dynamic> permNodes,
|
||||
SnAccountProfile? profile,
|
||||
DateTime? suspendedAt,
|
||||
DateTime updatedAt});
|
||||
int? affiliatedId,
|
||||
int? affiliatedTo,
|
||||
int? automatedBy,
|
||||
int? automatedId});
|
||||
|
||||
@override
|
||||
$SnAccountProfileCopyWith<$Res>? get profile;
|
||||
@@ -248,45 +249,49 @@ class __$$SnAccountImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? affiliatedId = freezed,
|
||||
Object? affiliatedTo = freezed,
|
||||
Object? automatedBy = freezed,
|
||||
Object? automatedId = freezed,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? deletedAt = freezed,
|
||||
Object? confirmedAt = freezed,
|
||||
Object? contacts = freezed,
|
||||
Object? avatar = null,
|
||||
Object? banner = null,
|
||||
Object? confirmedAt = freezed,
|
||||
Object? contacts = null,
|
||||
Object? createdAt = null,
|
||||
Object? deletedAt = freezed,
|
||||
Object? description = null,
|
||||
Object? name = null,
|
||||
Object? nick = null,
|
||||
Object? permNodes = null,
|
||||
Object? profile = freezed,
|
||||
Object? suspendedAt = freezed,
|
||||
Object? updatedAt = null,
|
||||
Object? affiliatedId = freezed,
|
||||
Object? affiliatedTo = freezed,
|
||||
Object? automatedBy = freezed,
|
||||
Object? automatedId = freezed,
|
||||
}) {
|
||||
return _then(_$SnAccountImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
affiliatedId: freezed == affiliatedId
|
||||
? _value.affiliatedId
|
||||
: affiliatedId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
affiliatedTo: freezed == affiliatedTo
|
||||
? _value.affiliatedTo
|
||||
: affiliatedTo // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedBy: freezed == automatedBy
|
||||
? _value.automatedBy
|
||||
: automatedBy // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedId: freezed == automatedId
|
||||
? _value.automatedId
|
||||
: automatedId // 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?,
|
||||
confirmedAt: freezed == confirmedAt
|
||||
? _value.confirmedAt
|
||||
: confirmedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
contacts: freezed == contacts
|
||||
? _value._contacts
|
||||
: contacts // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAccountContact>?,
|
||||
avatar: null == avatar
|
||||
? _value.avatar
|
||||
: avatar // ignore: cast_nullable_to_non_nullable
|
||||
@@ -295,22 +300,6 @@ class __$$SnAccountImplCopyWithImpl<$Res>
|
||||
? _value.banner
|
||||
: banner // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
confirmedAt: freezed == confirmedAt
|
||||
? _value.confirmedAt
|
||||
: confirmedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
contacts: null == contacts
|
||||
? _value._contacts
|
||||
: contacts // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAccountContact>,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
deletedAt: freezed == deletedAt
|
||||
? _value.deletedAt
|
||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
description: null == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
@@ -335,71 +324,81 @@ class __$$SnAccountImplCopyWithImpl<$Res>
|
||||
? _value.suspendedAt
|
||||
: suspendedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
affiliatedId: freezed == affiliatedId
|
||||
? _value.affiliatedId
|
||||
: affiliatedId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
affiliatedTo: freezed == affiliatedTo
|
||||
? _value.affiliatedTo
|
||||
: affiliatedTo // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedBy: freezed == automatedBy
|
||||
? _value.automatedBy
|
||||
: automatedBy // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
automatedId: freezed == automatedId
|
||||
? _value.automatedId
|
||||
: automatedId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$SnAccountImpl implements _SnAccount {
|
||||
class _$SnAccountImpl extends _SnAccount {
|
||||
const _$SnAccountImpl(
|
||||
{required this.id,
|
||||
required this.affiliatedId,
|
||||
required this.affiliatedTo,
|
||||
required this.automatedBy,
|
||||
required this.automatedId,
|
||||
{@HiveField(0) required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.confirmedAt,
|
||||
required final List<SnAccountContact>? contacts,
|
||||
required this.avatar,
|
||||
required this.banner,
|
||||
required this.confirmedAt,
|
||||
required final List<SnAccountContact> contacts,
|
||||
required this.createdAt,
|
||||
required this.deletedAt,
|
||||
required this.description,
|
||||
required this.name,
|
||||
required this.nick,
|
||||
required final Map<String, dynamic> permNodes,
|
||||
required this.profile,
|
||||
required this.suspendedAt,
|
||||
required this.updatedAt})
|
||||
required this.affiliatedId,
|
||||
required this.affiliatedTo,
|
||||
required this.automatedBy,
|
||||
required this.automatedId})
|
||||
: _contacts = contacts,
|
||||
_permNodes = permNodes;
|
||||
_permNodes = permNodes,
|
||||
super._();
|
||||
|
||||
factory _$SnAccountImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SnAccountImplFromJson(json);
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
@override
|
||||
final int? affiliatedId;
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
final int? affiliatedTo;
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
final int? automatedBy;
|
||||
final DateTime? deletedAt;
|
||||
@override
|
||||
final int? automatedId;
|
||||
final DateTime? confirmedAt;
|
||||
final List<SnAccountContact>? _contacts;
|
||||
@override
|
||||
List<SnAccountContact>? get contacts {
|
||||
final value = _contacts;
|
||||
if (value == null) return null;
|
||||
if (_contacts is EqualUnmodifiableListView) return _contacts;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
final String avatar;
|
||||
@override
|
||||
final String banner;
|
||||
@override
|
||||
final DateTime? confirmedAt;
|
||||
final List<SnAccountContact> _contacts;
|
||||
@override
|
||||
List<SnAccountContact> get contacts {
|
||||
if (_contacts is EqualUnmodifiableListView) return _contacts;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_contacts);
|
||||
}
|
||||
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
final DateTime? deletedAt;
|
||||
@override
|
||||
final String description;
|
||||
@override
|
||||
final String name;
|
||||
@@ -418,11 +417,17 @@ class _$SnAccountImpl implements _SnAccount {
|
||||
@override
|
||||
final DateTime? suspendedAt;
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
final int? affiliatedId;
|
||||
@override
|
||||
final int? affiliatedTo;
|
||||
@override
|
||||
final int? automatedBy;
|
||||
@override
|
||||
final int? automatedId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAccount(id: $id, affiliatedId: $affiliatedId, affiliatedTo: $affiliatedTo, automatedBy: $automatedBy, automatedId: $automatedId, avatar: $avatar, banner: $banner, confirmedAt: $confirmedAt, contacts: $contacts, createdAt: $createdAt, deletedAt: $deletedAt, description: $description, name: $name, nick: $nick, permNodes: $permNodes, profile: $profile, suspendedAt: $suspendedAt, updatedAt: $updatedAt)';
|
||||
return 'SnAccount(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, confirmedAt: $confirmedAt, contacts: $contacts, avatar: $avatar, banner: $banner, description: $description, name: $name, nick: $nick, permNodes: $permNodes, profile: $profile, suspendedAt: $suspendedAt, affiliatedId: $affiliatedId, affiliatedTo: $affiliatedTo, automatedBy: $automatedBy, automatedId: $automatedId)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -431,23 +436,17 @@ class _$SnAccountImpl implements _SnAccount {
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SnAccountImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.affiliatedId, affiliatedId) ||
|
||||
other.affiliatedId == affiliatedId) &&
|
||||
(identical(other.affiliatedTo, affiliatedTo) ||
|
||||
other.affiliatedTo == affiliatedTo) &&
|
||||
(identical(other.automatedBy, automatedBy) ||
|
||||
other.automatedBy == automatedBy) &&
|
||||
(identical(other.automatedId, automatedId) ||
|
||||
other.automatedId == automatedId) &&
|
||||
(identical(other.avatar, avatar) || other.avatar == avatar) &&
|
||||
(identical(other.banner, banner) || other.banner == banner) &&
|
||||
(identical(other.createdAt, createdAt) ||
|
||||
other.createdAt == createdAt) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
other.updatedAt == updatedAt) &&
|
||||
(identical(other.deletedAt, deletedAt) ||
|
||||
other.deletedAt == deletedAt) &&
|
||||
(identical(other.confirmedAt, confirmedAt) ||
|
||||
other.confirmedAt == confirmedAt) &&
|
||||
const DeepCollectionEquality().equals(other._contacts, _contacts) &&
|
||||
(identical(other.createdAt, createdAt) ||
|
||||
other.createdAt == createdAt) &&
|
||||
(identical(other.deletedAt, deletedAt) ||
|
||||
other.deletedAt == deletedAt) &&
|
||||
(identical(other.avatar, avatar) || other.avatar == avatar) &&
|
||||
(identical(other.banner, banner) || other.banner == banner) &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
@@ -457,8 +456,14 @@ class _$SnAccountImpl implements _SnAccount {
|
||||
(identical(other.profile, profile) || other.profile == profile) &&
|
||||
(identical(other.suspendedAt, suspendedAt) ||
|
||||
other.suspendedAt == suspendedAt) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
other.updatedAt == updatedAt));
|
||||
(identical(other.affiliatedId, affiliatedId) ||
|
||||
other.affiliatedId == affiliatedId) &&
|
||||
(identical(other.affiliatedTo, affiliatedTo) ||
|
||||
other.affiliatedTo == affiliatedTo) &&
|
||||
(identical(other.automatedBy, automatedBy) ||
|
||||
other.automatedBy == automatedBy) &&
|
||||
(identical(other.automatedId, automatedId) ||
|
||||
other.automatedId == automatedId));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -466,23 +471,23 @@ class _$SnAccountImpl implements _SnAccount {
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
affiliatedId,
|
||||
affiliatedTo,
|
||||
automatedBy,
|
||||
automatedId,
|
||||
avatar,
|
||||
banner,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
deletedAt,
|
||||
confirmedAt,
|
||||
const DeepCollectionEquality().hash(_contacts),
|
||||
createdAt,
|
||||
deletedAt,
|
||||
avatar,
|
||||
banner,
|
||||
description,
|
||||
name,
|
||||
nick,
|
||||
const DeepCollectionEquality().hash(_permNodes),
|
||||
profile,
|
||||
suspendedAt,
|
||||
updatedAt);
|
||||
affiliatedId,
|
||||
affiliatedTo,
|
||||
automatedBy,
|
||||
automatedId);
|
||||
|
||||
/// Create a copy of SnAccount
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -500,53 +505,49 @@ class _$SnAccountImpl implements _SnAccount {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SnAccount implements SnAccount {
|
||||
abstract class _SnAccount extends SnAccount {
|
||||
const factory _SnAccount(
|
||||
{required final int id,
|
||||
required final int? affiliatedId,
|
||||
required final int? affiliatedTo,
|
||||
required final int? automatedBy,
|
||||
required final int? automatedId,
|
||||
{@HiveField(0) required final int id,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
required final DateTime? deletedAt,
|
||||
required final DateTime? confirmedAt,
|
||||
required final List<SnAccountContact>? contacts,
|
||||
required final String avatar,
|
||||
required final String banner,
|
||||
required final DateTime? confirmedAt,
|
||||
required final List<SnAccountContact> contacts,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime? deletedAt,
|
||||
required final String description,
|
||||
required final String name,
|
||||
required final String nick,
|
||||
required final Map<String, dynamic> permNodes,
|
||||
required final SnAccountProfile? profile,
|
||||
required final DateTime? suspendedAt,
|
||||
required final DateTime updatedAt}) = _$SnAccountImpl;
|
||||
required final int? affiliatedId,
|
||||
required final int? affiliatedTo,
|
||||
required final int? automatedBy,
|
||||
required final int? automatedId}) = _$SnAccountImpl;
|
||||
const _SnAccount._() : super._();
|
||||
|
||||
factory _SnAccount.fromJson(Map<String, dynamic> json) =
|
||||
_$SnAccountImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
int get id;
|
||||
@override
|
||||
int? get affiliatedId;
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
int? get affiliatedTo;
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
int? get automatedBy;
|
||||
DateTime? get deletedAt;
|
||||
@override
|
||||
int? get automatedId;
|
||||
DateTime? get confirmedAt;
|
||||
@override
|
||||
List<SnAccountContact>? get contacts;
|
||||
@override
|
||||
String get avatar;
|
||||
@override
|
||||
String get banner;
|
||||
@override
|
||||
DateTime? get confirmedAt;
|
||||
@override
|
||||
List<SnAccountContact> get contacts;
|
||||
@override
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
DateTime? get deletedAt;
|
||||
@override
|
||||
String get description;
|
||||
@override
|
||||
String get name;
|
||||
@@ -559,7 +560,13 @@ abstract class _SnAccount implements SnAccount {
|
||||
@override
|
||||
DateTime? get suspendedAt;
|
||||
@override
|
||||
DateTime get updatedAt;
|
||||
int? get affiliatedId;
|
||||
@override
|
||||
int? get affiliatedTo;
|
||||
@override
|
||||
int? get automatedBy;
|
||||
@override
|
||||
int? get automatedId;
|
||||
|
||||
/// Create a copy of SnAccount
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -9,22 +9,19 @@ part of 'account.dart';
|
||||
_$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
|
||||
_$SnAccountImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
affiliatedId: (json['affiliated_id'] as num?)?.toInt(),
|
||||
affiliatedTo: (json['affiliated_to'] as num?)?.toInt(),
|
||||
automatedBy: (json['automated_by'] as num?)?.toInt(),
|
||||
automatedId: (json['automated_id'] as num?)?.toInt(),
|
||||
avatar: json['avatar'] as String,
|
||||
banner: json['banner'] as String,
|
||||
confirmedAt: json['confirmed_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['confirmed_at'] as String),
|
||||
contacts: (json['contacts'] as List<dynamic>)
|
||||
.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
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),
|
||||
confirmedAt: json['confirmed_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['confirmed_at'] as String),
|
||||
contacts: (json['contacts'] as List<dynamic>?)
|
||||
?.map((e) => SnAccountContact.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
avatar: json['avatar'] as String,
|
||||
banner: json['banner'] as String,
|
||||
description: json['description'] as String,
|
||||
name: json['name'] as String,
|
||||
nick: json['nick'] as String,
|
||||
@@ -35,29 +32,32 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
|
||||
suspendedAt: json['suspended_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['suspended_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
affiliatedId: (json['affiliated_id'] as num?)?.toInt(),
|
||||
affiliatedTo: (json['affiliated_to'] as num?)?.toInt(),
|
||||
automatedBy: (json['automated_by'] as num?)?.toInt(),
|
||||
automatedId: (json['automated_id'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnAccountImplToJson(_$SnAccountImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'affiliated_id': instance.affiliatedId,
|
||||
'affiliated_to': instance.affiliatedTo,
|
||||
'automated_by': instance.automatedBy,
|
||||
'automated_id': instance.automatedId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'confirmed_at': instance.confirmedAt?.toIso8601String(),
|
||||
'contacts': instance.contacts?.map((e) => e.toJson()).toList(),
|
||||
'avatar': instance.avatar,
|
||||
'banner': instance.banner,
|
||||
'confirmed_at': instance.confirmedAt?.toIso8601String(),
|
||||
'contacts': instance.contacts.map((e) => e.toJson()).toList(),
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'description': instance.description,
|
||||
'name': instance.name,
|
||||
'nick': instance.nick,
|
||||
'perm_nodes': instance.permNodes,
|
||||
'profile': instance.profile?.toJson(),
|
||||
'suspended_at': instance.suspendedAt?.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'affiliated_id': instance.affiliatedId,
|
||||
'affiliated_to': instance.affiliatedTo,
|
||||
'automated_by': instance.automatedBy,
|
||||
'automated_id': instance.automatedId,
|
||||
};
|
||||
|
||||
_$SnAccountContactImpl _$$SnAccountContactImplFromJson(
|
||||
|
||||
101
lib/types/chat.dart
Normal file
101
lib/types/chat.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
|
||||
part 'chat.freezed.dart';
|
||||
part 'chat.g.dart';
|
||||
|
||||
@freezed
|
||||
class SnChannel with _$SnChannel {
|
||||
const SnChannel._();
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
const factory SnChannel({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required dynamic deletedAt,
|
||||
@HiveField(4) required String alias,
|
||||
@HiveField(5) required String name,
|
||||
@HiveField(6) required String description,
|
||||
@HiveField(7) required List<dynamic>? members,
|
||||
List<SnChatMessage>? messages,
|
||||
dynamic calls,
|
||||
@HiveField(8) required int type,
|
||||
@HiveField(9) required int accountId,
|
||||
@HiveField(10) required SnRealm? realm,
|
||||
@HiveField(11) required int? realmId,
|
||||
@HiveField(12) required bool isPublic,
|
||||
@HiveField(13) required bool isCommunity,
|
||||
}) = _SnChannel;
|
||||
|
||||
factory SnChannel.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnChannelFromJson(json);
|
||||
|
||||
String get key => '${realm?.alias ?? 'global'}:$alias';
|
||||
String get keyPath => '${realm?.alias ?? 'global'}/$alias';
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SnChannelMember with _$SnChannelMember {
|
||||
const SnChannelMember._();
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
const factory SnChannelMember({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required DateTime? deletedAt,
|
||||
@HiveField(4) required int channelId,
|
||||
@HiveField(5) required int accountId,
|
||||
@HiveField(6) required String? nick,
|
||||
@HiveField(7) required SnChannel? channel,
|
||||
@HiveField(8) required SnAccount? account,
|
||||
@Default(0) int notify,
|
||||
@HiveField(9) required int powerLevel,
|
||||
dynamic calls,
|
||||
dynamic events,
|
||||
}) = _SnChannelMember;
|
||||
|
||||
factory SnChannelMember.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnChannelMemberFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SnChatMessage with _$SnChatMessage {
|
||||
const SnChatMessage._();
|
||||
|
||||
@HiveType(typeId: 4)
|
||||
const factory SnChatMessage({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required DateTime? deletedAt,
|
||||
@HiveField(4) required String uuid,
|
||||
@HiveField(5) required Map<String, dynamic> body,
|
||||
@HiveField(6) required String type,
|
||||
@HiveField(7) required SnChannel channel,
|
||||
@HiveField(8) required SnChannelMember sender,
|
||||
@HiveField(9) required int channelId,
|
||||
@HiveField(10) required int senderId,
|
||||
SnChatMessagePreload? preload,
|
||||
}) = _SnChatMessage;
|
||||
|
||||
factory SnChatMessage.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnChatMessageFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SnChatMessagePreload with _$SnChatMessagePreload {
|
||||
const SnChatMessagePreload._();
|
||||
|
||||
const factory SnChatMessagePreload({
|
||||
List<SnAttachment?>? attachments,
|
||||
SnChatMessage? quoteEvent,
|
||||
}) = _SnChatMessagePreload;
|
||||
|
||||
factory SnChatMessagePreload.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnChatMessagePreloadFromJson(json);
|
||||
}
|
||||
1739
lib/types/chat.freezed.dart
Normal file
1739
lib/types/chat.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
352
lib/types/chat.g.dart
Normal file
352
lib/types/chat.g.dart
Normal file
@@ -0,0 +1,352 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SnChannelImplAdapter extends TypeAdapter<_$SnChannelImpl> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
_$SnChannelImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnChannelImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as dynamic,
|
||||
alias: fields[4] as String,
|
||||
name: fields[5] as String,
|
||||
description: fields[6] as String,
|
||||
members: (fields[7] as List?)?.cast<dynamic>(),
|
||||
type: fields[8] as int,
|
||||
accountId: fields[9] as int,
|
||||
realm: fields[10] as SnRealm?,
|
||||
realmId: fields[11] as int?,
|
||||
isPublic: fields[12] as bool,
|
||||
isCommunity: fields[13] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnChannelImpl obj) {
|
||||
writer
|
||||
..writeByte(14)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.alias)
|
||||
..writeByte(5)
|
||||
..write(obj.name)
|
||||
..writeByte(6)
|
||||
..write(obj.description)
|
||||
..writeByte(8)
|
||||
..write(obj.type)
|
||||
..writeByte(9)
|
||||
..write(obj.accountId)
|
||||
..writeByte(10)
|
||||
..write(obj.realm)
|
||||
..writeByte(11)
|
||||
..write(obj.realmId)
|
||||
..writeByte(12)
|
||||
..write(obj.isPublic)
|
||||
..writeByte(13)
|
||||
..write(obj.isCommunity)
|
||||
..writeByte(7)
|
||||
..write(obj.members);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnChannelImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class SnChannelMemberImplAdapter extends TypeAdapter<_$SnChannelMemberImpl> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
_$SnChannelMemberImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnChannelMemberImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as DateTime?,
|
||||
channelId: fields[4] as int,
|
||||
accountId: fields[5] as int,
|
||||
nick: fields[6] as String?,
|
||||
channel: fields[7] as SnChannel?,
|
||||
account: fields[8] as SnAccount?,
|
||||
powerLevel: fields[9] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnChannelMemberImpl obj) {
|
||||
writer
|
||||
..writeByte(10)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.channelId)
|
||||
..writeByte(5)
|
||||
..write(obj.accountId)
|
||||
..writeByte(6)
|
||||
..write(obj.nick)
|
||||
..writeByte(7)
|
||||
..write(obj.channel)
|
||||
..writeByte(8)
|
||||
..write(obj.account)
|
||||
..writeByte(9)
|
||||
..write(obj.powerLevel);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnChannelMemberImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class SnChatMessageImplAdapter extends TypeAdapter<_$SnChatMessageImpl> {
|
||||
@override
|
||||
final int typeId = 4;
|
||||
|
||||
@override
|
||||
_$SnChatMessageImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnChatMessageImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as DateTime?,
|
||||
uuid: fields[4] as String,
|
||||
body: (fields[5] as Map).cast<String, dynamic>(),
|
||||
type: fields[6] as String,
|
||||
channel: fields[7] as SnChannel,
|
||||
sender: fields[8] as SnChannelMember,
|
||||
channelId: fields[9] as int,
|
||||
senderId: fields[10] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnChatMessageImpl obj) {
|
||||
writer
|
||||
..writeByte(11)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.uuid)
|
||||
..writeByte(6)
|
||||
..write(obj.type)
|
||||
..writeByte(7)
|
||||
..write(obj.channel)
|
||||
..writeByte(8)
|
||||
..write(obj.sender)
|
||||
..writeByte(9)
|
||||
..write(obj.channelId)
|
||||
..writeByte(10)
|
||||
..write(obj.senderId)
|
||||
..writeByte(5)
|
||||
..write(obj.body);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnChatMessageImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// 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'] as List<dynamic>?)
|
||||
?.map((e) => SnChatMessage.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
calls: json['calls'],
|
||||
type: (json['type'] as num).toInt(),
|
||||
accountId: (json['account_id'] as num).toInt(),
|
||||
realm: json['realm'] == null
|
||||
? null
|
||||
: SnRealm.fromJson(json['realm'] as Map<String, dynamic>),
|
||||
realmId: (json['realm_id'] as num?)?.toInt(),
|
||||
isPublic: json['is_public'] as bool,
|
||||
isCommunity: json['is_community'] as bool,
|
||||
);
|
||||
|
||||
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?.map((e) => e.toJson()).toList(),
|
||||
'calls': instance.calls,
|
||||
'type': instance.type,
|
||||
'account_id': instance.accountId,
|
||||
'realm': instance.realm?.toJson(),
|
||||
'realm_id': instance.realmId,
|
||||
'is_public': instance.isPublic,
|
||||
'is_community': instance.isCommunity,
|
||||
};
|
||||
|
||||
_$SnChannelMemberImpl _$$SnChannelMemberImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$SnChannelMemberImpl(
|
||||
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),
|
||||
channelId: (json['channel_id'] as num).toInt(),
|
||||
accountId: (json['account_id'] as num).toInt(),
|
||||
nick: json['nick'] as String?,
|
||||
channel: json['channel'] == null
|
||||
? null
|
||||
: SnChannel.fromJson(json['channel'] as Map<String, dynamic>),
|
||||
account: json['account'] == null
|
||||
? null
|
||||
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
||||
notify: (json['notify'] as num?)?.toInt() ?? 0,
|
||||
powerLevel: (json['power_level'] as num).toInt(),
|
||||
calls: json['calls'],
|
||||
events: json['events'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnChannelMemberImplToJson(
|
||||
_$SnChannelMemberImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'channel_id': instance.channelId,
|
||||
'account_id': instance.accountId,
|
||||
'nick': instance.nick,
|
||||
'channel': instance.channel?.toJson(),
|
||||
'account': instance.account?.toJson(),
|
||||
'notify': instance.notify,
|
||||
'power_level': instance.powerLevel,
|
||||
'calls': instance.calls,
|
||||
'events': instance.events,
|
||||
};
|
||||
|
||||
_$SnChatMessageImpl _$$SnChatMessageImplFromJson(Map<String, dynamic> json) =>
|
||||
_$SnChatMessageImpl(
|
||||
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),
|
||||
uuid: json['uuid'] as String,
|
||||
body: json['body'] as Map<String, dynamic>,
|
||||
type: json['type'] as String,
|
||||
channel: SnChannel.fromJson(json['channel'] as Map<String, dynamic>),
|
||||
sender: SnChannelMember.fromJson(json['sender'] as Map<String, dynamic>),
|
||||
channelId: (json['channel_id'] as num).toInt(),
|
||||
senderId: (json['sender_id'] as num).toInt(),
|
||||
preload: json['preload'] == null
|
||||
? null
|
||||
: SnChatMessagePreload.fromJson(
|
||||
json['preload'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnChatMessageImplToJson(_$SnChatMessageImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'uuid': instance.uuid,
|
||||
'body': instance.body,
|
||||
'type': instance.type,
|
||||
'channel': instance.channel.toJson(),
|
||||
'sender': instance.sender.toJson(),
|
||||
'channel_id': instance.channelId,
|
||||
'sender_id': instance.senderId,
|
||||
'preload': instance.preload?.toJson(),
|
||||
};
|
||||
|
||||
_$SnChatMessagePreloadImpl _$$SnChatMessagePreloadImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$SnChatMessagePreloadImpl(
|
||||
attachments: (json['attachments'] as List<dynamic>?)
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
quoteEvent: json['quote_event'] == null
|
||||
? null
|
||||
: SnChatMessage.fromJson(json['quote_event'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnChatMessagePreloadImplToJson(
|
||||
_$SnChatMessagePreloadImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||
'quote_event': instance.quoteEvent?.toJson(),
|
||||
};
|
||||
@@ -53,7 +53,7 @@ class SnPost with _$SnPost {
|
||||
@freezed
|
||||
class SnPostPreload with _$SnPostPreload {
|
||||
const factory SnPostPreload({
|
||||
required List<SnAttachment>? attachments,
|
||||
required List<SnAttachment?>? attachments,
|
||||
}) = _SnPostPreload;
|
||||
|
||||
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
||||
|
||||
@@ -953,7 +953,7 @@ SnPostPreload _$SnPostPreloadFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnPostPreload {
|
||||
List<SnAttachment>? get attachments => throw _privateConstructorUsedError;
|
||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnPostPreload to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -971,7 +971,7 @@ abstract class $SnPostPreloadCopyWith<$Res> {
|
||||
SnPostPreload value, $Res Function(SnPostPreload) then) =
|
||||
_$SnPostPreloadCopyWithImpl<$Res, SnPostPreload>;
|
||||
@useResult
|
||||
$Res call({List<SnAttachment>? attachments});
|
||||
$Res call({List<SnAttachment?>? attachments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -995,7 +995,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
||||
attachments: freezed == attachments
|
||||
? _value.attachments
|
||||
: attachments // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAttachment>?,
|
||||
as List<SnAttachment?>?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -1008,7 +1008,7 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
||||
__$$SnPostPreloadImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({List<SnAttachment>? attachments});
|
||||
$Res call({List<SnAttachment?>? attachments});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -1030,7 +1030,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
||||
attachments: freezed == attachments
|
||||
? _value._attachments
|
||||
: attachments // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAttachment>?,
|
||||
as List<SnAttachment?>?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1038,15 +1038,15 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||
const _$SnPostPreloadImpl({required final List<SnAttachment>? attachments})
|
||||
const _$SnPostPreloadImpl({required final List<SnAttachment?>? attachments})
|
||||
: _attachments = attachments;
|
||||
|
||||
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SnPostPreloadImplFromJson(json);
|
||||
|
||||
final List<SnAttachment>? _attachments;
|
||||
final List<SnAttachment?>? _attachments;
|
||||
@override
|
||||
List<SnAttachment>? get attachments {
|
||||
List<SnAttachment?>? get attachments {
|
||||
final value = _attachments;
|
||||
if (value == null) return null;
|
||||
if (_attachments is EqualUnmodifiableListView) return _attachments;
|
||||
@@ -1091,13 +1091,13 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||
|
||||
abstract class _SnPostPreload implements SnPostPreload {
|
||||
const factory _SnPostPreload(
|
||||
{required final List<SnAttachment>? attachments}) = _$SnPostPreloadImpl;
|
||||
{required final List<SnAttachment?>? attachments}) = _$SnPostPreloadImpl;
|
||||
|
||||
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
||||
_$SnPostPreloadImpl.fromJson;
|
||||
|
||||
@override
|
||||
List<SnAttachment>? get attachments;
|
||||
List<SnAttachment?>? get attachments;
|
||||
|
||||
/// Create a copy of SnPostPreload
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -103,13 +103,15 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
||||
_$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
||||
_$SnPostPreloadImpl(
|
||||
attachments: (json['attachments'] as List<dynamic>?)
|
||||
?.map((e) => SnAttachment.fromJson(e as Map<String, dynamic>))
|
||||
?.map((e) => e == null
|
||||
? null
|
||||
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'attachments': instance.attachments?.map((e) => e.toJson()).toList(),
|
||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||
};
|
||||
|
||||
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
||||
|
||||
50
lib/types/realm.dart
Normal file
50
lib/types/realm.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.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 SnRealm._();
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
const factory SnRealm({
|
||||
@HiveField(0) required int id,
|
||||
@HiveField(1) required DateTime createdAt,
|
||||
@HiveField(2) required DateTime updatedAt,
|
||||
@HiveField(3) required DateTime? deletedAt,
|
||||
@HiveField(4) required String alias,
|
||||
@HiveField(5) required String name,
|
||||
@HiveField(6) required String description,
|
||||
List<SnRealmMember>? members,
|
||||
@HiveField(7) required String? avatar,
|
||||
@HiveField(8) required String? banner,
|
||||
@HiveField(9) required Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) required int accountId,
|
||||
@HiveField(11) required bool isPublic,
|
||||
@HiveField(12) required bool isCommunity,
|
||||
}) = _SnRealm;
|
||||
|
||||
factory SnRealm.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnRealmFromJson(json);
|
||||
}
|
||||
854
lib/types/realm.freezed.dart
Normal file
854
lib/types/realm.freezed.dart
Normal file
@@ -0,0 +1,854 @@
|
||||
// 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 {
|
||||
@HiveField(0)
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
@HiveField(1)
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||
@HiveField(4)
|
||||
String get alias => throw _privateConstructorUsedError;
|
||||
@HiveField(5)
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
@HiveField(6)
|
||||
String get description => throw _privateConstructorUsedError;
|
||||
List<SnRealmMember>? get members => throw _privateConstructorUsedError;
|
||||
@HiveField(7)
|
||||
String? get avatar => throw _privateConstructorUsedError;
|
||||
@HiveField(8)
|
||||
String? get banner => throw _privateConstructorUsedError;
|
||||
@HiveField(9)
|
||||
Map<String, dynamic>? get accessPolicy => throw _privateConstructorUsedError;
|
||||
@HiveField(10)
|
||||
int get accountId => throw _privateConstructorUsedError;
|
||||
@HiveField(11)
|
||||
bool get isPublic => throw _privateConstructorUsedError;
|
||||
@HiveField(12)
|
||||
bool get isCommunity => 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(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) String alias,
|
||||
@HiveField(5) String name,
|
||||
@HiveField(6) String description,
|
||||
List<SnRealmMember>? members,
|
||||
@HiveField(7) String? avatar,
|
||||
@HiveField(8) String? banner,
|
||||
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) int accountId,
|
||||
@HiveField(11) bool isPublic,
|
||||
@HiveField(12) bool isCommunity});
|
||||
}
|
||||
|
||||
/// @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? accountId = null,
|
||||
Object? isPublic = null,
|
||||
Object? isCommunity = 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>?,
|
||||
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,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SnRealmImplCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
|
||||
factory _$$SnRealmImplCopyWith(
|
||||
_$SnRealmImpl value, $Res Function(_$SnRealmImpl) then) =
|
||||
__$$SnRealmImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@HiveField(0) int id,
|
||||
@HiveField(1) DateTime createdAt,
|
||||
@HiveField(2) DateTime updatedAt,
|
||||
@HiveField(3) DateTime? deletedAt,
|
||||
@HiveField(4) String alias,
|
||||
@HiveField(5) String name,
|
||||
@HiveField(6) String description,
|
||||
List<SnRealmMember>? members,
|
||||
@HiveField(7) String? avatar,
|
||||
@HiveField(8) String? banner,
|
||||
@HiveField(9) Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) int accountId,
|
||||
@HiveField(11) bool isPublic,
|
||||
@HiveField(12) bool isCommunity});
|
||||
}
|
||||
|
||||
/// @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? accountId = null,
|
||||
Object? isPublic = null,
|
||||
Object? isCommunity = 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>?,
|
||||
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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
@HiveType(typeId: 1)
|
||||
class _$SnRealmImpl extends _SnRealm {
|
||||
const _$SnRealmImpl(
|
||||
{@HiveField(0) required this.id,
|
||||
@HiveField(1) required this.createdAt,
|
||||
@HiveField(2) required this.updatedAt,
|
||||
@HiveField(3) required this.deletedAt,
|
||||
@HiveField(4) required this.alias,
|
||||
@HiveField(5) required this.name,
|
||||
@HiveField(6) required this.description,
|
||||
final List<SnRealmMember>? members,
|
||||
@HiveField(7) required this.avatar,
|
||||
@HiveField(8) required this.banner,
|
||||
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) required this.accountId,
|
||||
@HiveField(11) required this.isPublic,
|
||||
@HiveField(12) required this.isCommunity})
|
||||
: _members = members,
|
||||
_accessPolicy = accessPolicy,
|
||||
super._();
|
||||
|
||||
factory _$SnRealmImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SnRealmImplFromJson(json);
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
final DateTime? deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
final String alias;
|
||||
@override
|
||||
@HiveField(5)
|
||||
final String name;
|
||||
@override
|
||||
@HiveField(6)
|
||||
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
|
||||
@HiveField(7)
|
||||
final String? avatar;
|
||||
@override
|
||||
@HiveField(8)
|
||||
final String? banner;
|
||||
final Map<String, dynamic>? _accessPolicy;
|
||||
@override
|
||||
@HiveField(9)
|
||||
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
|
||||
@HiveField(10)
|
||||
final int accountId;
|
||||
@override
|
||||
@HiveField(11)
|
||||
final bool isPublic;
|
||||
@override
|
||||
@HiveField(12)
|
||||
final bool isCommunity;
|
||||
|
||||
@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, accountId: $accountId, isPublic: $isPublic, isCommunity: $isCommunity)';
|
||||
}
|
||||
|
||||
@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.accountId, accountId) ||
|
||||
other.accountId == accountId) &&
|
||||
(identical(other.isPublic, isPublic) ||
|
||||
other.isPublic == isPublic) &&
|
||||
(identical(other.isCommunity, isCommunity) ||
|
||||
other.isCommunity == isCommunity));
|
||||
}
|
||||
|
||||
@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),
|
||||
accountId,
|
||||
isPublic,
|
||||
isCommunity);
|
||||
|
||||
/// 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 extends SnRealm {
|
||||
const factory _SnRealm(
|
||||
{@HiveField(0) required final int id,
|
||||
@HiveField(1) required final DateTime createdAt,
|
||||
@HiveField(2) required final DateTime updatedAt,
|
||||
@HiveField(3) required final DateTime? deletedAt,
|
||||
@HiveField(4) required final String alias,
|
||||
@HiveField(5) required final String name,
|
||||
@HiveField(6) required final String description,
|
||||
final List<SnRealmMember>? members,
|
||||
@HiveField(7) required final String? avatar,
|
||||
@HiveField(8) required final String? banner,
|
||||
@HiveField(9) required final Map<String, dynamic>? accessPolicy,
|
||||
@HiveField(10) required final int accountId,
|
||||
@HiveField(11) required final bool isPublic,
|
||||
@HiveField(12) required final bool isCommunity}) = _$SnRealmImpl;
|
||||
const _SnRealm._() : super._();
|
||||
|
||||
factory _SnRealm.fromJson(Map<String, dynamic> json) = _$SnRealmImpl.fromJson;
|
||||
|
||||
@override
|
||||
@HiveField(0)
|
||||
int get id;
|
||||
@override
|
||||
@HiveField(1)
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
@HiveField(2)
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
@HiveField(3)
|
||||
DateTime? get deletedAt;
|
||||
@override
|
||||
@HiveField(4)
|
||||
String get alias;
|
||||
@override
|
||||
@HiveField(5)
|
||||
String get name;
|
||||
@override
|
||||
@HiveField(6)
|
||||
String get description;
|
||||
@override
|
||||
List<SnRealmMember>? get members;
|
||||
@override
|
||||
@HiveField(7)
|
||||
String? get avatar;
|
||||
@override
|
||||
@HiveField(8)
|
||||
String? get banner;
|
||||
@override
|
||||
@HiveField(9)
|
||||
Map<String, dynamic>? get accessPolicy;
|
||||
@override
|
||||
@HiveField(10)
|
||||
int get accountId;
|
||||
@override
|
||||
@HiveField(11)
|
||||
bool get isPublic;
|
||||
@override
|
||||
@HiveField(12)
|
||||
bool get isCommunity;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
149
lib/types/realm.g.dart
Normal file
149
lib/types/realm.g.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'realm.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SnRealmImplAdapter extends TypeAdapter<_$SnRealmImpl> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
_$SnRealmImpl read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return _$SnRealmImpl(
|
||||
id: fields[0] as int,
|
||||
createdAt: fields[1] as DateTime,
|
||||
updatedAt: fields[2] as DateTime,
|
||||
deletedAt: fields[3] as DateTime?,
|
||||
alias: fields[4] as String,
|
||||
name: fields[5] as String,
|
||||
description: fields[6] as String,
|
||||
avatar: fields[7] as String?,
|
||||
banner: fields[8] as String?,
|
||||
accessPolicy: (fields[9] as Map?)?.cast<String, dynamic>(),
|
||||
accountId: fields[10] as int,
|
||||
isPublic: fields[11] as bool,
|
||||
isCommunity: fields[12] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, _$SnRealmImpl obj) {
|
||||
writer
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(2)
|
||||
..write(obj.updatedAt)
|
||||
..writeByte(3)
|
||||
..write(obj.deletedAt)
|
||||
..writeByte(4)
|
||||
..write(obj.alias)
|
||||
..writeByte(5)
|
||||
..write(obj.name)
|
||||
..writeByte(6)
|
||||
..write(obj.description)
|
||||
..writeByte(7)
|
||||
..write(obj.avatar)
|
||||
..writeByte(8)
|
||||
..write(obj.banner)
|
||||
..writeByte(10)
|
||||
..write(obj.accountId)
|
||||
..writeByte(11)
|
||||
..write(obj.isPublic)
|
||||
..writeByte(12)
|
||||
..write(obj.isCommunity)
|
||||
..writeByte(9)
|
||||
..write(obj.accessPolicy);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SnRealmImplAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// 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>?,
|
||||
accountId: (json['account_id'] as num).toInt(),
|
||||
isPublic: json['is_public'] as bool,
|
||||
isCommunity: json['is_community'] as bool,
|
||||
);
|
||||
|
||||
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,
|
||||
'account_id': instance.accountId,
|
||||
'is_public': instance.isPublic,
|
||||
'is_community': instance.isCommunity,
|
||||
};
|
||||
@@ -14,7 +14,7 @@ import 'package:surface/widgets/universal_image.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class AttachmentItem extends StatelessWidget {
|
||||
final SnAttachment data;
|
||||
final SnAttachment? data;
|
||||
final bool isExpandable;
|
||||
const AttachmentItem({
|
||||
super.key,
|
||||
@@ -23,15 +23,19 @@ class AttachmentItem extends StatelessWidget {
|
||||
});
|
||||
|
||||
Widget _buildContent(BuildContext context, String heroTag) {
|
||||
final tp = data.mimetype.split('/').firstOrNull;
|
||||
if (data == null) {
|
||||
return const Icon(Symbols.cancel).center();
|
||||
}
|
||||
|
||||
final tp = data!.mimetype.split('/').firstOrNull;
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
switch (tp) {
|
||||
case 'image':
|
||||
return Hero(
|
||||
tag: 'attachment-${data.rid}-$heroTag',
|
||||
tag: 'attachment-${data!.rid}-$heroTag',
|
||||
child: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(data.rid),
|
||||
key: Key('attachment-${data.rid}-$heroTag'),
|
||||
sn.getAttachmentUrl(data!.rid),
|
||||
key: Key('attachment-${data!.rid}-$heroTag'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
@@ -45,7 +49,7 @@ class AttachmentItem extends StatelessWidget {
|
||||
final uuid = Uuid();
|
||||
final heroTag = uuid.v4();
|
||||
|
||||
if (data.isMature) {
|
||||
if (data!.isMature) {
|
||||
return _AttachmentItemSensitiveBlur(
|
||||
child: _buildContent(context, heroTag),
|
||||
);
|
||||
@@ -56,7 +60,7 @@ class AttachmentItem extends StatelessWidget {
|
||||
child: _buildContent(context, heroTag),
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
AttachmentDetailPopup(data: data, heroTag: heroTag),
|
||||
AttachmentDetailPopup(data: data!, heroTag: heroTag),
|
||||
rootNavigator: true,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -6,14 +6,16 @@ import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||
|
||||
class AttachmentList extends StatelessWidget {
|
||||
final List<SnAttachment> data;
|
||||
final bool? bordered;
|
||||
final List<SnAttachment?> data;
|
||||
final bool bordered;
|
||||
final bool noGrow;
|
||||
final double? maxHeight;
|
||||
final EdgeInsets? listPadding;
|
||||
const AttachmentList({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.bordered,
|
||||
this.bordered = false,
|
||||
this.noGrow = false,
|
||||
this.maxHeight,
|
||||
this.listPadding,
|
||||
});
|
||||
@@ -23,7 +25,7 @@ class AttachmentList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final borderSide = (bordered ?? false)
|
||||
final borderSide = bordered
|
||||
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
|
||||
: BorderSide.none;
|
||||
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
||||
@@ -34,7 +36,7 @@ class AttachmentList extends StatelessWidget {
|
||||
|
||||
if (data.isEmpty) return const SizedBox.shrink();
|
||||
if (data.length == 1) {
|
||||
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
||||
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || noGrow) {
|
||||
return Padding(
|
||||
// Single child list-like displaying
|
||||
padding: listPadding ?? EdgeInsets.zero,
|
||||
@@ -46,7 +48,7 @@ class AttachmentList extends StatelessWidget {
|
||||
borderRadius: kDefaultRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: data[0].metadata['ratio']?.toDouble() ?? 1,
|
||||
aspectRatio: data[0]?.metadata['ratio']?.toDouble() ?? 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: kDefaultRadius,
|
||||
child: AttachmentItem(data: data[0], isExpandable: true),
|
||||
@@ -62,7 +64,7 @@ class AttachmentList extends StatelessWidget {
|
||||
border: Border(top: borderSide, bottom: borderSide),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: data[0].metadata['ratio']?.toDouble() ?? 1,
|
||||
aspectRatio: data[0]?.metadata['ratio']?.toDouble() ?? 1,
|
||||
child: AttachmentItem(data: data[0], isExpandable: true),
|
||||
),
|
||||
);
|
||||
@@ -86,7 +88,7 @@ class AttachmentList extends StatelessWidget {
|
||||
borderRadius: kDefaultRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: data[idx].metadata['ratio']?.toDouble() ?? 1,
|
||||
aspectRatio: data[idx]?.metadata['ratio']?.toDouble() ?? 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: kDefaultRadius,
|
||||
child:
|
||||
|
||||
126
lib/widgets/chat/chat_message.dart
Normal file
126
lib/widgets/chat/chat_message.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
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/user_directory.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:swipe_to/swipe_to.dart';
|
||||
|
||||
class ChatMessage extends StatelessWidget {
|
||||
final SnChatMessage data;
|
||||
final bool isCompact;
|
||||
final bool isMerged;
|
||||
final bool hasMerged;
|
||||
final bool isPending;
|
||||
final Function()? onReply;
|
||||
const ChatMessage({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.isCompact = false,
|
||||
this.isMerged = false,
|
||||
this.hasMerged = false,
|
||||
this.isPending = false,
|
||||
this.onReply,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
final user = ud.getAccountFromCache(data.sender.accountId);
|
||||
|
||||
final dateFormatter = DateFormat('MM/dd HH:mm');
|
||||
|
||||
return SwipeTo(
|
||||
key: Key('chat-message-${data.id}'),
|
||||
iconOnLeftSwipe: Symbols.reply,
|
||||
swipeSensitivity: 20,
|
||||
onLeftSwipe: onReply != null ? (_) => onReply!() : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isMerged && !isCompact)
|
||||
AccountImage(
|
||||
content: user?.avatar,
|
||||
)
|
||||
else if (isMerged)
|
||||
const Gap(40),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isMerged)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
if (isCompact)
|
||||
AccountImage(
|
||||
content: user?.avatar,
|
||||
radius: 12,
|
||||
).padding(right: 6),
|
||||
Text(
|
||||
(data.sender.nick?.isNotEmpty ?? false)
|
||||
? data.sender.nick!
|
||||
: user!.nick,
|
||||
).bold(),
|
||||
const Gap(6),
|
||||
Text(
|
||||
dateFormatter.format(data.createdAt.toLocal()),
|
||||
).fontSize(13),
|
||||
],
|
||||
),
|
||||
if (isCompact) const Gap(4),
|
||||
if (data.preload?.quoteEvent != null)
|
||||
StyledWidget(Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 4,
|
||||
right: 4,
|
||||
top: 8,
|
||||
bottom: 6,
|
||||
),
|
||||
child: ChatMessage(
|
||||
data: data.preload!.quoteEvent!,
|
||||
isCompact: true,
|
||||
),
|
||||
)).padding(bottom: 4, top: isMerged ? 4 : 2),
|
||||
if (data.body['text'] != null)
|
||||
MarkdownTextContent(
|
||||
content: data.body['text'],
|
||||
isAutoWarp: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
).opacity(isPending ? 0.5 : 1),
|
||||
if (data.preload?.attachments?.isNotEmpty ?? false)
|
||||
AttachmentList(
|
||||
data: data.preload!.attachments!,
|
||||
bordered: true,
|
||||
noGrow: true,
|
||||
maxHeight: 520,
|
||||
listPadding: const EdgeInsets.only(top: 8),
|
||||
),
|
||||
if (!hasMerged && !isCompact) const Gap(12),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
234
lib/widgets/chat/chat_message_input.dart
Normal file
234
lib/widgets/chat/chat_message_input.dart
Normal file
@@ -0,0 +1,234 @@
|
||||
import 'package:easy_localization/easy_localization.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:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/controllers/chat_message_controller.dart';
|
||||
import 'package:surface/controllers/post_write_controller.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||
|
||||
class ChatMessageInput extends StatefulWidget {
|
||||
final ChatMessageController controller;
|
||||
const ChatMessageInput({super.key, required this.controller});
|
||||
|
||||
@override
|
||||
State<ChatMessageInput> createState() => ChatMessageInputState();
|
||||
}
|
||||
|
||||
class ChatMessageInputState extends State<ChatMessageInput> {
|
||||
bool _isBusy = false;
|
||||
double? _progress;
|
||||
|
||||
SnChatMessage? _replyingMessage;
|
||||
|
||||
final TextEditingController _contentController = TextEditingController();
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
void setReply(SnChatMessage? value) {
|
||||
setState(() => _replyingMessage = value);
|
||||
}
|
||||
|
||||
Future<void> _sendMessage() async {
|
||||
if (_isBusy) return;
|
||||
|
||||
final attach = context.read<SnAttachmentProvider>();
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
for (int i = 0; i < _attachments.length; i++) {
|
||||
final media = _attachments[i];
|
||||
if (media.attachment != null) continue; // Already uploaded, skip
|
||||
if (media.isEmpty) continue; // Nothing to do, skip
|
||||
|
||||
final place = await attach.chunkedUploadInitialize(
|
||||
(await media.length())!,
|
||||
media.name,
|
||||
'interactive',
|
||||
null,
|
||||
);
|
||||
|
||||
final item = await attach.chunkedUploadParts(
|
||||
media.toFile()!,
|
||||
place.$1,
|
||||
place.$2,
|
||||
onProgress: (progress) {
|
||||
// Calculate overall progress for attachments
|
||||
setState(() {
|
||||
progress = (i + progress) / _attachments.length;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
_attachments[i] = PostWriteMedia(item);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
setState(() => _isBusy = false);
|
||||
context.showErrorDialog(err);
|
||||
return;
|
||||
}
|
||||
|
||||
attach.putCache(
|
||||
_attachments.where((e) => e.attachment != null).map((e) => e.attachment!),
|
||||
noCheck: true,
|
||||
);
|
||||
|
||||
// Send the message
|
||||
// NOTICE This future should not be awaited, so that the message can be sent in the background and the user can continue to type
|
||||
widget.controller.sendMessage(
|
||||
'messages.new',
|
||||
_contentController.text,
|
||||
attachments: _attachments
|
||||
.where((e) => e.attachment != null)
|
||||
.map((e) => e.attachment!.rid)
|
||||
.toList(),
|
||||
quoteId: _replyingMessage?.id,
|
||||
);
|
||||
_contentController.clear();
|
||||
_attachments.clear();
|
||||
_replyingMessage = null;
|
||||
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
|
||||
final List<PostWriteMedia> _attachments = List.empty(growable: true);
|
||||
final _imagePicker = ImagePicker();
|
||||
|
||||
void _selectMedia() async {
|
||||
final result = await _imagePicker.pickMultipleMedia();
|
||||
if (result.isEmpty) return;
|
||||
_attachments.addAll(
|
||||
result.map((e) => PostWriteMedia.fromFile(e)),
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_contentController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (_isBusy && _progress != null)
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: _progress),
|
||||
duration: Duration(milliseconds: 300),
|
||||
builder: (context, value, _) =>
|
||||
LinearProgressIndicator(value: value, minHeight: 2),
|
||||
)
|
||||
else if (_isBusy)
|
||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||
Padding(
|
||||
padding: _attachments.isNotEmpty
|
||||
? const EdgeInsets.only(top: 8)
|
||||
: EdgeInsets.zero,
|
||||
child: PostMediaPendingListRaw(
|
||||
attachments: _attachments,
|
||||
isBusy: _isBusy,
|
||||
onUpdate: (idx, updatedMedia) async {
|
||||
setState(() => _attachments[idx] = updatedMedia);
|
||||
},
|
||||
onRemove: (idx) async {
|
||||
setState(() => _attachments.removeAt(idx));
|
||||
},
|
||||
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
||||
),
|
||||
).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||
SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: _replyingMessage != null
|
||||
? const EdgeInsets.only(top: 8)
|
||||
: EdgeInsets.zero,
|
||||
child: _replyingMessage != null
|
||||
? MaterialBanner(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
leading: const Icon(Symbols.reply),
|
||||
content: SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_replyingMessage?.body['text'] != null)
|
||||
MarkdownTextContent(
|
||||
content: _replyingMessage?.body['text'],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('cancel'.tr()),
|
||||
onPressed: () {
|
||||
setState(() => _replyingMessage = null);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
).height(_replyingMessage != null ? 54 + 8 : 0, animate: true).animate(
|
||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
focusNode: _focusNode,
|
||||
controller: _contentController,
|
||||
decoration: InputDecoration(
|
||||
isCollapsed: true,
|
||||
hintText: 'fieldChatMessage'.tr(args: [
|
||||
widget.controller.channel?.name ?? 'loading'.tr()
|
||||
]),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onSubmitted: (_) {
|
||||
if (_isBusy) return;
|
||||
_sendMessage();
|
||||
_focusNode.requestFocus();
|
||||
},
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
IconButton(
|
||||
onPressed: _isBusy ? null : _selectMedia,
|
||||
icon: Icon(
|
||||
Symbols.add_photo_alternate,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _isBusy ? null : _sendMessage,
|
||||
icon: Icon(
|
||||
Symbols.send,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(horizontal: 16),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -6,12 +6,72 @@ 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,
|
||||
});
|
||||
|
||||
Widget _buildWithBackgroundImage(
|
||||
BuildContext context,
|
||||
File imageFile,
|
||||
Widget child,
|
||||
) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
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(
|
||||
builder: (context, constraints) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
backgroundBlendMode: BlendMode.darken,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
image: DecorationImage(
|
||||
opacity: 0.2,
|
||||
image: ResizeImage(
|
||||
FileImage(imageFile),
|
||||
width: (constraints.maxWidth * devicePixelRatio).round(),
|
||||
height: (constraints.maxHeight * devicePixelRatio).round(),
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
return ScaffoldMessenger(
|
||||
child: FutureBuilder(
|
||||
future:
|
||||
@@ -21,32 +81,7 @@ class AppBackground extends StatelessWidget {
|
||||
final path = '${snapshot.data!.path}/app_background_image';
|
||||
final file = File(path);
|
||||
if (file.existsSync()) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
backgroundBlendMode: BlendMode.darken,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
image: DecorationImage(
|
||||
opacity: 0.2,
|
||||
image: ResizeImage(
|
||||
FileImage(file),
|
||||
width: (constraints.maxWidth * devicePixelRatio)
|
||||
.round(),
|
||||
height: (constraints.maxHeight * devicePixelRatio)
|
||||
.round(),
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
return _buildWithBackgroundImage(context, file, child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
||||
preload: SnPostPreload(
|
||||
attachments: attachments
|
||||
.where(
|
||||
(ele) => out[i].body['attachments']?.contains(ele.rid) ?? false,
|
||||
(ele) =>
|
||||
out[i].body['attachments']?.contains(ele?.rid) ?? false,
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
|
||||
@@ -18,54 +18,22 @@ import 'package:surface/widgets/dialog.dart';
|
||||
|
||||
class PostMediaPendingList extends StatelessWidget {
|
||||
final PostWriteController controller;
|
||||
|
||||
const PostMediaPendingList({super.key, required this.controller});
|
||||
|
||||
void _cropImage(BuildContext context, int idx) async {
|
||||
final media = controller.attachments[idx];
|
||||
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
||||
? await showCupertinoImageCropper(
|
||||
// ignore: use_build_context_synchronously
|
||||
context,
|
||||
// ignore: use_build_context_synchronously
|
||||
imageProvider: media.getImageProvider(context)!,
|
||||
)
|
||||
: await showMaterialImageCropper(
|
||||
// ignore: use_build_context_synchronously
|
||||
context,
|
||||
// ignore: use_build_context_synchronously
|
||||
imageProvider: media.getImageProvider(context)!,
|
||||
);
|
||||
|
||||
if (result == null) return;
|
||||
if (!context.mounted) return;
|
||||
|
||||
Future<void> _handleUpdate(int idx, PostWriteMedia updatedMedia) async {
|
||||
controller.setIsBusy(true);
|
||||
|
||||
final rawBytes =
|
||||
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
|
||||
.buffer
|
||||
.asUint8List();
|
||||
controller.setAttachmentAt(
|
||||
idx,
|
||||
PostWriteMedia.fromBytes(rawBytes, media.name, media.type),
|
||||
);
|
||||
|
||||
controller.setIsBusy(false);
|
||||
try {
|
||||
controller.setAttachmentAt(idx, updatedMedia);
|
||||
} finally {
|
||||
controller.setIsBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteAttachment(BuildContext context, int idx) async {
|
||||
final media = controller.attachments[idx];
|
||||
if (media.attachment == null) return;
|
||||
|
||||
Future<void> _handleRemove(int idx) async {
|
||||
controller.setIsBusy(true);
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.delete('/cgi/uc/attachments/${media.attachment!.id}');
|
||||
controller.removeAttachmentAt(idx);
|
||||
} catch (err) {
|
||||
if (!context.mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
controller.setIsBusy(false);
|
||||
}
|
||||
@@ -73,108 +41,180 @@ class PostMediaPendingList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
return ListenableBuilder(
|
||||
listenable: controller,
|
||||
builder: (context, _) {
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxHeight: 120),
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
separatorBuilder: (context, index) => const Gap(8),
|
||||
itemCount: controller.attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final media = controller.attachments[idx];
|
||||
return ContextMenuRegion(
|
||||
contextMenu: ContextMenu(
|
||||
entries: [
|
||||
if (media.type == PostWriteMediaType.image &&
|
||||
media.attachment != null)
|
||||
MenuItem(
|
||||
label: 'preview'.tr(),
|
||||
icon: Symbols.preview,
|
||||
onSelected: () {
|
||||
context.pushTransparentRoute(
|
||||
AttachmentDetailPopup(data: media.attachment!),
|
||||
rootNavigator: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (media.type == PostWriteMediaType.image &&
|
||||
media.attachment == null)
|
||||
MenuItem(
|
||||
label: 'crop'.tr(),
|
||||
icon: Symbols.crop,
|
||||
onSelected: () => _cropImage(context, idx),
|
||||
),
|
||||
if (media.attachment != null)
|
||||
MenuItem(
|
||||
label: 'delete'.tr(),
|
||||
icon: Symbols.delete,
|
||||
onSelected: controller.isBusy
|
||||
? null
|
||||
: () => _deleteAttachment(context, idx),
|
||||
),
|
||||
if (media.attachment == null)
|
||||
MenuItem(
|
||||
label: 'delete'.tr(),
|
||||
icon: Symbols.delete,
|
||||
onSelected: () {
|
||||
controller.removeAttachmentAt(idx);
|
||||
},
|
||||
)
|
||||
else
|
||||
MenuItem(
|
||||
label: 'unlink'.tr(),
|
||||
icon: Symbols.link_off,
|
||||
onSelected: () {
|
||||
controller.removeAttachmentAt(idx);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: switch (media.type) {
|
||||
PostWriteMediaType.image =>
|
||||
LayoutBuilder(builder: (context, constraints) {
|
||||
return Image(
|
||||
image: media.getImageProvider(
|
||||
context,
|
||||
width: (constraints.maxWidth * devicePixelRatio)
|
||||
.round(),
|
||||
height:
|
||||
(constraints.maxHeight * devicePixelRatio)
|
||||
.round(),
|
||||
)!,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
}),
|
||||
_ => Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Icon(Symbols.docs).center(),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
return PostMediaPendingListRaw(
|
||||
attachments: controller.attachments,
|
||||
isBusy: controller.isBusy,
|
||||
onUpdate: (idx, updatedMedia) => _handleUpdate(idx, updatedMedia),
|
||||
onRemove: (idx) => _handleRemove(idx),
|
||||
onUpdateBusy: (state) => controller.setIsBusy(state),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PostMediaPendingListRaw extends StatelessWidget {
|
||||
final List<PostWriteMedia> attachments;
|
||||
final bool isBusy;
|
||||
final Future<void> Function(int idx, PostWriteMedia updatedMedia)? onUpdate;
|
||||
final Future<void> Function(int idx)? onRemove;
|
||||
final void Function(bool state)? onUpdateBusy;
|
||||
|
||||
const PostMediaPendingListRaw({
|
||||
super.key,
|
||||
required this.attachments,
|
||||
required this.isBusy,
|
||||
this.onUpdate,
|
||||
this.onRemove,
|
||||
this.onUpdateBusy,
|
||||
});
|
||||
|
||||
Future<void> _cropImage(BuildContext context, int idx) async {
|
||||
final media = attachments[idx];
|
||||
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
|
||||
? await showCupertinoImageCropper(
|
||||
context,
|
||||
imageProvider: media.getImageProvider(context)!,
|
||||
)
|
||||
: await showMaterialImageCropper(
|
||||
context,
|
||||
imageProvider: media.getImageProvider(context)!,
|
||||
);
|
||||
|
||||
if (result == null) return;
|
||||
|
||||
final rawBytes =
|
||||
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
|
||||
.buffer
|
||||
.asUint8List();
|
||||
|
||||
if (onUpdate != null) {
|
||||
final updatedMedia = PostWriteMedia.fromBytes(
|
||||
rawBytes,
|
||||
media.name,
|
||||
media.type,
|
||||
);
|
||||
await onUpdate!(idx, updatedMedia);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteAttachment(BuildContext context, int idx) async {
|
||||
final media = attachments[idx];
|
||||
if (media.attachment == null) return;
|
||||
|
||||
try {
|
||||
onUpdateBusy?.call(true);
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.delete('/cgi/uc/attachments/${media.attachment!.id}');
|
||||
onRemove!(idx);
|
||||
} catch (err) {
|
||||
if (!context.mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
onUpdateBusy?.call(false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxHeight: 120),
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
separatorBuilder: (context, index) => const Gap(8),
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final media = attachments[idx];
|
||||
return ContextMenuRegion(
|
||||
contextMenu: ContextMenu(
|
||||
entries: [
|
||||
if (media.type == PostWriteMediaType.image &&
|
||||
media.attachment != null)
|
||||
MenuItem(
|
||||
label: 'preview'.tr(),
|
||||
icon: Symbols.preview,
|
||||
onSelected: () {
|
||||
context.pushTransparentRoute(
|
||||
AttachmentDetailPopup(data: media.attachment!),
|
||||
rootNavigator: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (media.type == PostWriteMediaType.image &&
|
||||
media.attachment == null)
|
||||
MenuItem(
|
||||
label: 'crop'.tr(),
|
||||
icon: Symbols.crop,
|
||||
onSelected: () => _cropImage(context, idx),
|
||||
),
|
||||
if (media.attachment != null && onRemove != null)
|
||||
MenuItem(
|
||||
label: 'delete'.tr(),
|
||||
icon: Symbols.delete,
|
||||
onSelected:
|
||||
isBusy ? null : () => _deleteAttachment(context, idx),
|
||||
),
|
||||
if (media.attachment == null && onRemove != null)
|
||||
MenuItem(
|
||||
label: 'delete'.tr(),
|
||||
icon: Symbols.delete,
|
||||
onSelected: () {
|
||||
onRemove!(idx);
|
||||
},
|
||||
)
|
||||
else if (onRemove != null)
|
||||
MenuItem(
|
||||
label: 'unlink'.tr(),
|
||||
icon: Symbols.link_off,
|
||||
onSelected: () {
|
||||
onRemove!(idx);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: switch (media.type) {
|
||||
PostWriteMediaType.image =>
|
||||
LayoutBuilder(builder: (context, constraints) {
|
||||
return Image(
|
||||
image: media.getImageProvider(
|
||||
context,
|
||||
width: (constraints.maxWidth * devicePixelRatio)
|
||||
.round(),
|
||||
height: (constraints.maxHeight * devicePixelRatio)
|
||||
.round(),
|
||||
)!,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
}),
|
||||
_ => Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Icon(Symbols.docs).center(),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
@@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin");
|
||||
isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
flutter_secure_storage_linux
|
||||
isar_flutter_libs
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import Foundation
|
||||
import connectivity_plus
|
||||
import file_selector_macos
|
||||
import flutter_secure_storage_macos
|
||||
import isar_flutter_libs
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
@@ -17,6 +18,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
||||
56
pubspec.lock
56
pubspec.lock
@@ -516,10 +516,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_native_splash
|
||||
sha256: ee5c9bd2b74ea8676442fd4ab876b5d41681df49276488854d6c81a5377c0ef1
|
||||
sha256: "1152ab0067ca5a2ebeb862fe0a762057202cceb22b7e62692dcbabf6483891bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -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:
|
||||
@@ -794,6 +818,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
isar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isar
|
||||
sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0+1"
|
||||
isar_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isar_flutter_libs
|
||||
sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0+1"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -910,10 +950,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_symbols_icons
|
||||
sha256: "7b723abea4ad37e16fe921f1f1971cbb9b0f66d223a8c99981168a2306416b98"
|
||||
sha256: "1dea2aef1c83434f832f14341a5ffa1254e76b68e4d90333f95f8a2643bf1024"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2791.1"
|
||||
version: "4.2799.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1303,6 +1343,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
swipe_to:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: swipe_to
|
||||
sha256: "58f61031803ece9b0efe09006809e78904c640c6d42d48715d1d1c3c28f8499a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -74,6 +74,11 @@ dependencies:
|
||||
collection: ^1.18.0
|
||||
mime: ^2.0.0
|
||||
web_socket_channel: ^3.0.1
|
||||
isar: ^3.1.0+1
|
||||
isar_flutter_libs: ^3.1.0+1
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
swipe_to: ^1.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -90,6 +95,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
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
@@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
IsarFlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
file_selector_windows
|
||||
flutter_secure_storage_windows
|
||||
isar_flutter_libs
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user