Basic message sending and listing

This commit is contained in:
LittleSheep 2024-11-17 01:16:54 +08:00
parent e9fbd0c65f
commit 285bb42b09
19 changed files with 2220 additions and 378 deletions

View File

@ -0,0 +1,183 @@
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_network.dart';
import 'package:surface/providers/user_directory.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;
ChatMessageController(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_ud = context.read<UserDirectoryProvider>();
}
bool isPending = true;
bool isLoading = false;
int? messageTotal;
bool get isAllLoaded =>
messageTotal != null && messages.length >= messageTotal!;
String? _boxKey;
SnChannel? _channel;
List<SnChatMessage> messages = List.empty(growable: true);
Box<SnChatMessage>? get _box =>
(_boxKey == null || isPending) ? null : Hive.box<SnChatMessage>(_boxKey!);
Future<void> initialize(SnChannel channel) async {
_channel = channel;
_boxKey = '$kChatMessageBoxPrefix${channel.id}';
await Hive.openBox<SnChatMessage>(_boxKey!);
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> _addMessage(SnChatMessage message) async {
messages.add(message);
notifyListeners();
if (_box == null) return;
await _box!.put(message.id, message);
}
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 resp = await _sn.client.post(
'/cgi/im/channels/${_channel!.keyPath}/messages',
data: {
'type': type,
'uuid': nonce,
'body': {
'text': content,
'algorithm': 'plain',
if (quoteId != null) 'quote_id': quoteId,
if (relatedId != null) 'related_id': relatedId,
if (attachments != null) 'attachments': attachments,
},
},
);
final out = SnChatMessage.fromJson(
resp.data['data'] as Map<String, dynamic>,
);
await _addMessage(out);
}
/// 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) {
await loadMessages();
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 {
isLoading = false;
notifyListeners();
}
}
/// 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 {
if (_box != null) {
// Try retrieve these messages from the local storage
if (_box!.length >= take + offset || forceLocal) {
return _box!.values.skip(offset).take(take).toList();
}
}
final resp = await _sn.client.get(
'/cgi/im/channels/${_channel!.keyPath}/events',
queryParameters: {
'take': take,
'offset': offset,
},
);
messageTotal = resp.data['count'] as int?;
final out = List<SnChatMessage>.from(
resp.data['data']?.map((e) => SnChatMessage.fromJson(e)) ?? [],
);
_saveMessageToLocal(out);
// 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();
}
}
close() {
_box?.close();
}
}

View File

@ -12,10 +12,12 @@ 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();
@ -23,6 +25,9 @@ void main() async {
await Hive.initFlutter();
Hive.registerAdapter(SnChannelImplAdapter());
Hive.registerAdapter(SnRealmImplAdapter());
Hive.registerAdapter(SnChannelMemberImplAdapter());
Hive.registerAdapter(SnChatMessageImplAdapter());
if (!kReleaseMode) {
debugInvertOversizedImages = true;
@ -53,6 +58,7 @@ 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)),

View File

@ -28,8 +28,11 @@ class ChatChannelProvider extends ChangeNotifier {
});
}
Future<List<SnChannel>> _fetchChannelsFromServer(
{scope = 'global', direct = false}) async {
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: {
@ -39,15 +42,37 @@ class ChatChannelProvider extends ChangeNotifier {
final out = List<SnChannel>.from(
resp.data?.map((e) => SnChannel.fromJson(e)) ?? [],
);
_saveChannelToLocal(out);
if (!doNotSave) _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.
/// 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();
@ -55,18 +80,34 @@ class ChatChannelProvider extends ChangeNotifier {
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 dm =
await _fetchChannelsFromServer(scope: scopeToFetch.first, direct: true);
result.addAll(dm);
final directMessages = await _fetchChannelsFromServer(
scope: scopeToFetch.first,
direct: true,
);
result.addAll(directMessages);
for (final scope in scopeToFetch) {
final channel =
await _fetchChannelsFromServer(scope: scope, direct: false);
result.addAll(channel);
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;

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

View File

@ -10,6 +10,7 @@ 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';
@ -102,6 +103,16 @@ final _appRoutes = [
child: const ChatScreen(),
),
routes: [
GoRoute(
path: '/chat/:scope/:alias',
name: 'chatRoom',
builder: (context, state) => AppBackground(
child: ChatRoomScreen(
scope: state.pathParameters['scope']!,
alias: state.pathParameters['alias']!,
),
),
),
GoRoute(
path: '/chat/manage',
name: 'chatManage',

View File

@ -6,6 +6,7 @@ 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 StatefulWidget {
@ -27,10 +28,13 @@ class _ChatScreenState extends State<ChatScreen> {
chan.fetchChannels().listen((channels) {
if (mounted) setState(() => _channels = channels);
})
..onError((_) {
..onError((err) {
if (!mounted) return;
context.showErrorDialog(err);
setState(() => _isBusy = false);
})
..onDone(() {
if (!mounted) return;
setState(() => _isBusy = false);
});
}
@ -67,6 +71,15 @@ class _ChatScreenState extends State<ChatScreen> {
content: null,
fallbackWidget: const Icon(Symbols.chat, size: 20),
),
onTap: () {
GoRouter.of(context).pushNamed(
'chatRoom',
pathParameters: {
'scope': channel.realm?.alias ?? 'global',
'alias': channel.alias,
},
);
},
);
},
),

102
lib/screens/chat/room.dart Normal file
View File

@ -0,0 +1,102 @@
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;
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(
hasReachedMax: _messageController.isAllLoaded,
itemCount: _messageController.messages.length,
isLoading: _messageController.isLoading,
onFetchData: () {
_messageController.loadMessages();
},
itemBuilder: (context, idx) {
final message = _messageController.messages[idx];
return ChatMessage(data: message);
},
),
),
if (!_messageController.isPending)
Material(
elevation: 2,
child: ChatMessageInput(controller: _messageController)
.padding(bottom: MediaQuery.of(context).padding.bottom),
),
],
);
},
),
);
}
}

View File

@ -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) =>

View File

@ -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.

View File

@ -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(

View File

@ -1,5 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:surface/types/account.dart';
import 'package:surface/types/realm.dart';
part 'chat.freezed.dart';
@ -18,8 +19,8 @@ class SnChannel with _$SnChannel {
@HiveField(4) required String alias,
@HiveField(5) required String name,
@HiveField(6) required String description,
@HiveField(7) required List<dynamic> members,
dynamic messages,
@HiveField(7) required List<dynamic>? members,
List<SnChatMessage>? messages,
dynamic calls,
@HiveField(8) required int type,
@HiveField(9) required int accountId,
@ -33,4 +34,53 @@ class SnChannel with _$SnChannel {
_$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,
}) = _SnChatMessage;
factory SnChatMessage.fromJson(Map<String, dynamic> json) =>
_$SnChatMessageFromJson(json);
}

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@ class SnChannelImplAdapter extends TypeAdapter<_$SnChannelImpl> {
alias: fields[4] as String,
name: fields[5] as String,
description: fields[6] as String,
members: (fields[7] as List).cast<dynamic>(),
members: (fields[7] as List?)?.cast<dynamic>(),
type: fields[8] as int,
accountId: fields[9] as int,
realm: fields[10] as SnRealm?,
@ -79,6 +79,131 @@ class SnChannelImplAdapter extends TypeAdapter<_$SnChannelImpl> {
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
// **************************************************************************
@ -92,8 +217,10 @@ _$SnChannelImpl _$$SnChannelImplFromJson(Map<String, dynamic> json) =>
alias: json['alias'] as String,
name: json['name'] as String,
description: json['description'] as String,
members: json['members'] as List<dynamic>,
messages: json['messages'],
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(),
@ -115,7 +242,7 @@ Map<String, dynamic> _$$SnChannelImplToJson(_$SnChannelImpl instance) =>
'name': instance.name,
'description': instance.description,
'members': instance.members,
'messages': instance.messages,
'messages': instance.messages?.map((e) => e.toJson()).toList(),
'calls': instance.calls,
'type': instance.type,
'account_id': instance.accountId,
@ -124,3 +251,77 @@ Map<String, dynamic> _$$SnChannelImplToJson(_$SnChannelImpl instance) =>
'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(),
);
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,
};

View File

@ -1,4 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:surface/types/account.dart';
part 'realm.freezed.dart';
@ -24,21 +25,24 @@ class SnRealmMember with _$SnRealmMember {
@freezed
class SnRealm with _$SnRealm {
const SnRealm._();
@HiveType(typeId: 1)
const factory SnRealm({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String alias,
required String name,
required String description,
required List<SnRealmMember>? members,
required String? avatar,
required String? banner,
required Map<String, dynamic>? accessPolicy,
required bool isPublic,
required bool isCommunity,
required int accountId,
@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) =>

View File

@ -367,20 +367,33 @@ SnRealm _$SnRealmFromJson(Map<String, dynamic> 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;
bool get isPublic => throw _privateConstructorUsedError;
bool get isCommunity => 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;
@ -397,20 +410,20 @@ abstract class $SnRealmCopyWith<$Res> {
_$SnRealmCopyWithImpl<$Res, SnRealm>;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
String description,
{@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,
String? avatar,
String? banner,
Map<String, dynamic>? accessPolicy,
bool isPublic,
bool isCommunity,
int accountId});
@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
@ -439,9 +452,9 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
Object? avatar = freezed,
Object? banner = freezed,
Object? accessPolicy = freezed,
Object? accountId = null,
Object? isPublic = null,
Object? isCommunity = null,
Object? accountId = null,
}) {
return _then(_value.copyWith(
id: null == id
@ -488,6 +501,10 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
? _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
@ -496,10 +513,6 @@ class _$SnRealmCopyWithImpl<$Res, $Val extends SnRealm>
? _value.isCommunity
: isCommunity // ignore: cast_nullable_to_non_nullable
as bool,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
@ -512,20 +525,20 @@ abstract class _$$SnRealmImplCopyWith<$Res> implements $SnRealmCopyWith<$Res> {
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String alias,
String name,
String description,
{@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,
String? avatar,
String? banner,
Map<String, dynamic>? accessPolicy,
bool isPublic,
bool isCommunity,
int accountId});
@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
@ -552,9 +565,9 @@ class __$$SnRealmImplCopyWithImpl<$Res>
Object? avatar = freezed,
Object? banner = freezed,
Object? accessPolicy = freezed,
Object? accountId = null,
Object? isPublic = null,
Object? isCommunity = null,
Object? accountId = null,
}) {
return _then(_$SnRealmImpl(
id: null == id
@ -601,6 +614,10 @@ class __$$SnRealmImplCopyWithImpl<$Res>
? _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
@ -609,51 +626,56 @@ class __$$SnRealmImplCopyWithImpl<$Res>
? _value.isCommunity
: isCommunity // ignore: cast_nullable_to_non_nullable
as bool,
accountId: null == accountId
? _value.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnRealmImpl implements _SnRealm {
@HiveType(typeId: 1)
class _$SnRealmImpl extends _SnRealm {
const _$SnRealmImpl(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.alias,
required this.name,
required this.description,
required final List<SnRealmMember>? members,
required this.avatar,
required this.banner,
required final Map<String, dynamic>? accessPolicy,
required this.isPublic,
required this.isCommunity,
required this.accountId})
{@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;
_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
@ -666,11 +688,14 @@ class _$SnRealmImpl implements _SnRealm {
}
@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;
@ -680,15 +705,18 @@ class _$SnRealmImpl implements _SnRealm {
}
@override
@HiveField(10)
final int accountId;
@override
@HiveField(11)
final bool isPublic;
@override
@HiveField(12)
final bool isCommunity;
@override
final int accountId;
@override
String toString() {
return 'SnRealm(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, members: $members, avatar: $avatar, banner: $banner, accessPolicy: $accessPolicy, isPublic: $isPublic, isCommunity: $isCommunity, accountId: $accountId)';
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
@ -712,12 +740,12 @@ class _$SnRealmImpl implements _SnRealm {
(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) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
other.isCommunity == isCommunity));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@ -735,9 +763,9 @@ class _$SnRealmImpl implements _SnRealm {
avatar,
banner,
const DeepCollectionEquality().hash(_accessPolicy),
accountId,
isPublic,
isCommunity,
accountId);
isCommunity);
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.
@ -755,53 +783,67 @@ class _$SnRealmImpl implements _SnRealm {
}
}
abstract class _SnRealm implements SnRealm {
abstract class _SnRealm extends SnRealm {
const factory _SnRealm(
{required final int id,
required final DateTime createdAt,
required final DateTime updatedAt,
required final DateTime? deletedAt,
required final String alias,
required final String name,
required final String description,
required final List<SnRealmMember>? members,
required final String? avatar,
required final String? banner,
required final Map<String, dynamic>? accessPolicy,
required final bool isPublic,
required final bool isCommunity,
required final int accountId}) = _$SnRealmImpl;
{@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;
@override
int get accountId;
/// Create a copy of SnRealm
/// with the given fields replaced by the non-null parameter values.

View File

@ -2,6 +2,80 @@
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
// **************************************************************************
@ -51,9 +125,9 @@ _$SnRealmImpl _$$SnRealmImplFromJson(Map<String, dynamic> json) =>
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,
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
@ -69,7 +143,7 @@ Map<String, dynamic> _$$SnRealmImplToJson(_$SnRealmImpl instance) =>
'avatar': instance.avatar,
'banner': instance.banner,
'access_policy': instance.accessPolicy,
'account_id': instance.accountId,
'is_public': instance.isPublic,
'is_community': instance.isCommunity,
'account_id': instance.accountId,
};

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.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/markdown_content.dart';
class ChatMessage extends StatelessWidget {
final SnChatMessage data;
const ChatMessage({super.key, required this.data});
@override
Widget build(BuildContext context) {
final ud = context.read<UserDirectoryProvider>();
final user = ud.getAccountFromCache(data.sender.accountId);
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountImage(
content: user?.avatar,
),
const Gap(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
(data.sender.nick?.isNotEmpty ?? false)
? data.sender.nick!
: user!.nick,
).bold(),
if (data.body['text'] != null)
MarkdownTextContent(content: data.body['text']),
],
),
)
],
);
}
}

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/chat_message_controller.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> {
final TextEditingController _contentController = TextEditingController();
final FocusNode _focusNode = FocusNode();
void _sendMessage() {
widget.controller.sendMessage(
'messages.new',
_contentController.text,
);
_contentController.clear();
}
@override
void dispose() {
_contentController.dispose();
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 72,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Expanded(
child: TextField(
focusNode: _focusNode,
controller: _contentController,
decoration: InputDecoration(
isCollapsed: true,
hintText: 'Type a message...',
border: InputBorder.none,
),
onSubmitted: (_) {
_sendMessage();
_focusNode.requestFocus();
},
),
),
const Gap(8),
IconButton(
onPressed: _sendMessage,
icon: Icon(
Symbols.send,
color: Theme.of(context).colorScheme.primary,
),
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -4,
),
),
],
).padding(horizontal: 16, vertical: 12),
],
),
);
}
}

View File

@ -950,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: