Chat room details, invitions and members management

This commit is contained in:
LittleSheep 2025-05-03 20:42:37 +08:00
parent e2e6de965b
commit efdddf72e4
26 changed files with 1915 additions and 201 deletions

View File

@ -33,7 +33,7 @@
"managedPublisher": "Managed Publishers",
"createPublisher": "Create a Publisher",
"createPublisherHint": "To create posts, collections, etc.",
"editPublisher": "Edit a Publisher",
"editPublisher": "Edit Publisher",
"syncPublisher": "Use Account Data",
"create": "Create",
"edit": "Edit",
@ -62,7 +62,7 @@
"realms": "Realms",
"createRealm": "Create a Realm",
"createRealmHint": "Meet friends with same interests, build communities, and more.",
"editRealm": "Edit a Realm",
"editRealm": "Edit Realm",
"deleteRealm": "Delete Realm {}",
"deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
"explore": "Explore",
@ -72,9 +72,20 @@
"slug": "Slug",
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
"createChatRoom": "Create a Room",
"editChatRoom": "Edit a Room",
"editChatRoom": "Edit Room",
"deleteChatRoom": "Delete Room",
"chat": "Chat",
"chatMessageHint": "Message in {}",
"chatDirectMessageHint": "Message to {}",
"loading": "Loading..."
"loading": "Loading...",
"descriptionNone": "No description yet.",
"invites": "Invites",
"invitesEmpty": "No invites yet, such a lonely person...",
"chatMembers": {
"one": "{} member",
"other": "{} members"
},
"permissionOwner": "Owner",
"permissionModerator": "Moderator",
"permissionMember": "Member"
}

View File

@ -7,30 +7,13 @@ import 'package:uuid/uuid.dart';
class MessageRepository {
final SnChat room;
final SnChatMember identity;
final Dio _apiClient;
final AppDatabase _database;
SnChatMember? _identity;
final Map<String, LocalChatMessage> pendingMessages = {};
MessageRepository(this.room, this._apiClient, this._database) {
initialize();
}
bool initialized = false;
Future<void> initialize() async {
if (initialized) return;
try {
final response = await _apiClient.get('/chat/${room.id}/members/me');
_identity = SnChatMember.fromJson(response.data);
initialized = true;
} catch (e) {
rethrow;
}
}
MessageRepository(this.room, this.identity, this._apiClient, this._database);
Future<List<LocalChatMessage>> listMessages({
int offset = 0,
@ -143,12 +126,6 @@ class MessageRepository {
List<SnCloudFile>? attachments,
Map<String, dynamic>? meta,
}) async {
if (!initialized || _identity == null) {
throw UnsupportedError(
"The message repository is not ready for send message.",
);
}
// Generate a unique nonce for this message
final nonce = const Uuid().v4();
@ -156,12 +133,12 @@ class MessageRepository {
final mockMessage = SnChatMessage(
id: 'pending_$nonce',
chatRoomId: roomId,
senderId: _identity!.id,
senderId: identity.id,
content: content,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
nonce: nonce,
sender: _identity!,
sender: identity,
);
final localMessage = LocalChatMessage.fromRemoteMessage(

View File

@ -81,6 +81,7 @@ abstract class SnChatMember with _$SnChatMember {
required DateTime? deletedAt,
required String id,
required int chatRoomId,
required SnChat? chatRoom,
required int accountId,
required SnAccount account,
required String? nick,

View File

@ -709,7 +709,7 @@ $SnChatMemberCopyWith<$Res> get sender {
/// @nodoc
mixin _$SnChatMember {
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; int get chatRoomId; int get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot;
DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; int get chatRoomId; SnChat? get chatRoom; int get accountId; SnAccount get account; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot;
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -722,16 +722,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,accountId,account,nick,role,notify,joinedAt,isBot);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
@override
String toString() {
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
}
@ -742,11 +742,11 @@ abstract mixin class $SnChatMemberCopyWith<$Res> {
factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl;
@useResult
$Res call({
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, int chatRoomId, int accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, int chatRoomId, SnChat? chatRoom, int accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
});
$SnAccountCopyWith<$Res> get account;
$SnChatCopyWith<$Res>? get chatRoom;$SnAccountCopyWith<$Res> get account;
}
/// @nodoc
@ -759,14 +759,15 @@ class _$SnChatMemberCopyWithImpl<$Res>
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
return _then(_self.copyWith(
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable
as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as int,chatRoom: freezed == chatRoom ? _self.chatRoom : chatRoom // ignore: cast_nullable_to_non_nullable
as SnChat?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as int,account: null == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
@ -780,6 +781,18 @@ as bool,
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnChatCopyWith<$Res>? get chatRoom {
if (_self.chatRoom == null) {
return null;
}
return $SnChatCopyWith<$Res>(_self.chatRoom!, (value) {
return _then(_self.copyWith(chatRoom: value));
});
}/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {
@ -793,7 +806,7 @@ $SnAccountCopyWith<$Res> get account {
@JsonSerializable()
class _SnChatMember implements SnChatMember {
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot});
const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.chatRoom, required this.accountId, required this.account, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot});
factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json);
@override final DateTime createdAt;
@ -801,6 +814,7 @@ class _SnChatMember implements SnChatMember {
@override final DateTime? deletedAt;
@override final String id;
@override final int chatRoomId;
@override final SnChat? chatRoom;
@override final int accountId;
@override final SnAccount account;
@override final String? nick;
@ -822,16 +836,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,accountId,account,nick,role,notify,joinedAt,isBot);
int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,chatRoom,accountId,account,nick,role,notify,joinedAt,isBot);
@override
String toString() {
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, chatRoom: $chatRoom, accountId: $accountId, account: $account, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)';
}
@ -842,11 +856,11 @@ abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWi
factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl;
@override @useResult
$Res call({
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, int chatRoomId, int accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, int chatRoomId, SnChat? chatRoom, int accountId, SnAccount account, String? nick, int role, int notify, DateTime? joinedAt, bool isBot
});
@override $SnAccountCopyWith<$Res> get account;
@override $SnChatCopyWith<$Res>? get chatRoom;@override $SnAccountCopyWith<$Res> get account;
}
/// @nodoc
@ -859,14 +873,15 @@ class __$SnChatMemberCopyWithImpl<$Res>
/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? chatRoom = freezed,Object? accountId = null,Object? account = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) {
return _then(_SnChatMember(
createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable
as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as int,chatRoom: freezed == chatRoom ? _self.chatRoom : chatRoom // ignore: cast_nullable_to_non_nullable
as SnChat?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as int,account: null == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
@ -881,6 +896,18 @@ as bool,
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnChatCopyWith<$Res>? get chatRoom {
if (_self.chatRoom == null) {
return null;
}
return $SnChatCopyWith<$Res>(_self.chatRoom!, (value) {
return _then(_self.copyWith(chatRoom: value));
});
}/// Create a copy of SnChatMember
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res> get account {
return $SnAccountCopyWith<$Res>(_self.account, (value) {

View File

@ -163,6 +163,10 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['deleted_at'] as String),
id: json['id'] as String,
chatRoomId: (json['chat_room_id'] as num).toInt(),
chatRoom:
json['chat_room'] == null
? null
: SnChat.fromJson(json['chat_room'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
account: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
nick: json['nick'] as String?,
@ -182,6 +186,7 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) =>
'deleted_at': instance.deletedAt?.toIso8601String(),
'id': instance.id,
'chat_room_id': instance.chatRoomId,
'chat_room': instance.chatRoom?.toJson(),
'account_id': instance.accountId,
'account': instance.account.toJson(),
'nick': instance.nick,

View File

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
@ -21,6 +23,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
final user = SnAccount.fromJson(response.data);
state = AsyncValue.data(user);
} catch (error, stackTrace) {
log("[UserInfo] Failed to fetch user info: $error");
state = AsyncValue.error(error, stackTrace);
}
}
@ -29,6 +32,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
state = const AsyncValue.data(null);
final prefs = _ref.read(sharedPreferencesProvider);
await prefs.remove(kTokenPairStoreKey);
_ref.refresh(userInfoProvider.notifier);
}
}

View File

@ -1,4 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -8,6 +11,7 @@ import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
part 'websocket.freezed.dart';
part 'websocket.g.dart';
@freezed
class WebSocketState with _$WebSocketState {
@ -17,15 +21,35 @@ class WebSocketState with _$WebSocketState {
const factory WebSocketState.error(String message) = _Error;
}
@freezed
abstract class WebSocketPacket with _$WebSocketPacket {
const factory WebSocketPacket({
required String type,
required Map<String, dynamic>? data,
required String? errorMessage,
}) = _WebSocketPacket;
factory WebSocketPacket.fromJson(Map<String, dynamic> json) =>
_$WebSocketPacketFromJson(json);
}
final websocketProvider = Provider<WebSocketService>((ref) {
return WebSocketService();
});
class WebSocketService {
WebSocketChannel? _channel;
Stream<dynamic>? _broadcastStream;
final StreamController<WebSocketPacket> _streamController =
StreamController<WebSocketPacket>.broadcast();
String? _lastUrl;
String? _lastAtk;
Timer? _reconnectTimer;
Stream<WebSocketPacket> get dataStream => _streamController.stream;
Future<void> connect(String url, String atk) async {
_lastUrl = url;
_lastAtk = atk;
log('[WebSocket] Trying connecting to $url');
try {
_channel = IOWebSocketChannel.connect(
@ -33,20 +57,48 @@ class WebSocketService {
headers: {'Authorization': 'Bearer $atk'},
);
await _channel!.ready;
_broadcastStream = _channel!.stream.asBroadcastStream();
_channel!.stream.listen(
(data) {
final dataStr =
data is Uint8List ? utf8.decode(data) : data.toString();
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
_streamController.sink.add(packet);
log("[WebSocket] Received packet: ${packet.type}");
},
onDone: () {
log('[WebSocket] Connection closed, attempting to reconnect...');
_scheduleReconnect();
},
onError: (error) {
log('[WebSocket] Error occurred: $error, attempting to reconnect...');
_scheduleReconnect();
},
);
} catch (err) {
log('[WebSocket] Failed to connect: $err');
_scheduleReconnect();
}
}
void _scheduleReconnect() {
_reconnectTimer?.cancel();
_reconnectTimer = Timer(const Duration(milliseconds: 500), () {
if (_lastUrl != null && _lastAtk != null) {
connect(_lastUrl!, _lastAtk!);
}
});
}
WebSocketChannel? get ws => _channel;
Stream<dynamic> get stream => _broadcastStream!;
void sendMessage(String message) {
_channel!.sink.add(message);
}
void close() {
_reconnectTimer?.cancel();
_lastUrl = null;
_lastAtk = null;
_channel?.sink.close();
}
}

View File

@ -202,6 +202,153 @@ as String,
}
}
/// @nodoc
mixin _$WebSocketPacket {
String get type; Map<String, dynamic>? get data; String? get errorMessage;
/// Create a copy of WebSocketPacket
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$WebSocketPacketCopyWith<WebSocketPacket> get copyWith => _$WebSocketPacketCopyWithImpl<WebSocketPacket>(this as WebSocketPacket, _$identity);
/// Serializes this WebSocketPacket to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WebSocketPacket&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(data),errorMessage);
@override
String toString() {
return 'WebSocketPacket(type: $type, data: $data, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class $WebSocketPacketCopyWith<$Res> {
factory $WebSocketPacketCopyWith(WebSocketPacket value, $Res Function(WebSocketPacket) _then) = _$WebSocketPacketCopyWithImpl;
@useResult
$Res call({
String type, Map<String, dynamic>? data, String? errorMessage
});
}
/// @nodoc
class _$WebSocketPacketCopyWithImpl<$Res>
implements $WebSocketPacketCopyWith<$Res> {
_$WebSocketPacketCopyWithImpl(this._self, this._then);
final WebSocketPacket _self;
final $Res Function(WebSocketPacket) _then;
/// Create a copy of WebSocketPacket
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? data = freezed,Object? errorMessage = freezed,}) {
return _then(_self.copyWith(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _WebSocketPacket implements WebSocketPacket {
const _WebSocketPacket({required this.type, required final Map<String, dynamic>? data, required this.errorMessage}): _data = data;
factory _WebSocketPacket.fromJson(Map<String, dynamic> json) => _$WebSocketPacketFromJson(json);
@override final String type;
final Map<String, dynamic>? _data;
@override Map<String, dynamic>? get data {
final value = _data;
if (value == null) return null;
if (_data is EqualUnmodifiableMapView) return _data;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override final String? errorMessage;
/// Create a copy of WebSocketPacket
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$WebSocketPacketCopyWith<_WebSocketPacket> get copyWith => __$WebSocketPacketCopyWithImpl<_WebSocketPacket>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$WebSocketPacketToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebSocketPacket&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._data, _data)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_data),errorMessage);
@override
String toString() {
return 'WebSocketPacket(type: $type, data: $data, errorMessage: $errorMessage)';
}
}
/// @nodoc
abstract mixin class _$WebSocketPacketCopyWith<$Res> implements $WebSocketPacketCopyWith<$Res> {
factory _$WebSocketPacketCopyWith(_WebSocketPacket value, $Res Function(_WebSocketPacket) _then) = __$WebSocketPacketCopyWithImpl;
@override @useResult
$Res call({
String type, Map<String, dynamic>? data, String? errorMessage
});
}
/// @nodoc
class __$WebSocketPacketCopyWithImpl<$Res>
implements _$WebSocketPacketCopyWith<$Res> {
__$WebSocketPacketCopyWithImpl(this._self, this._then);
final _WebSocketPacket _self;
final $Res Function(_WebSocketPacket) _then;
/// Create a copy of WebSocketPacket
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? data = freezed,Object? errorMessage = freezed,}) {
return _then(_WebSocketPacket(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,data: freezed == data ? _self._data : data // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

21
lib/pods/websocket.g.dart Normal file
View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'websocket.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_WebSocketPacket _$WebSocketPacketFromJson(Map<String, dynamic> json) =>
_WebSocketPacket(
type: json['type'] as String,
data: json['data'] as Map<String, dynamic>?,
errorMessage: json['error_message'] as String?,
);
Map<String, dynamic> _$WebSocketPacketToJson(_WebSocketPacket instance) =>
<String, dynamic>{
'type': instance.type,
'data': instance.data,
'error_message': instance.errorMessage,
};

View File

@ -37,5 +37,6 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: NewChatRoute.page, path: '/chat/new'),
AutoRoute(page: EditChatRoute.page, path: '/chat/:id/edit'),
AutoRoute(page: ChatRoomRoute.page, path: '/chat/:id'),
AutoRoute(page: ChatDetailRoute.page, path: '/chat/:id/detail'),
];
}

View File

@ -9,32 +9,33 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i14;
import 'package:flutter/material.dart' as _i15;
import 'package:island/models/post.dart' as _i16;
import 'package:auto_route/auto_route.dart' as _i15;
import 'package:flutter/material.dart' as _i16;
import 'package:island/models/post.dart' as _i17;
import 'package:island/screens/account.dart' as _i1;
import 'package:island/screens/account/me.dart' as _i9;
import 'package:island/screens/account/me/publishers.dart' as _i5;
import 'package:island/screens/account/me/update.dart' as _i13;
import 'package:island/screens/auth/create_account.dart' as _i4;
import 'package:island/screens/auth/login.dart' as _i8;
import 'package:island/screens/auth/tabs.dart' as _i12;
import 'package:island/screens/chat/chat.dart' as _i2;
import 'package:island/screens/chat/room.dart' as _i3;
import 'package:island/screens/explore.dart' as _i7;
import 'package:island/screens/posts/compose.dart' as _i10;
import 'package:island/screens/posts/detail.dart' as _i11;
import 'package:island/screens/realm/realms.dart' as _i6;
import 'package:island/screens/account/me.dart' as _i10;
import 'package:island/screens/account/me/publishers.dart' as _i6;
import 'package:island/screens/account/me/update.dart' as _i14;
import 'package:island/screens/auth/create_account.dart' as _i5;
import 'package:island/screens/auth/login.dart' as _i9;
import 'package:island/screens/auth/tabs.dart' as _i13;
import 'package:island/screens/chat/chat.dart' as _i3;
import 'package:island/screens/chat/room.dart' as _i4;
import 'package:island/screens/chat/room_detail.dart' as _i2;
import 'package:island/screens/explore.dart' as _i8;
import 'package:island/screens/posts/compose.dart' as _i11;
import 'package:island/screens/posts/detail.dart' as _i12;
import 'package:island/screens/realm/realms.dart' as _i7;
/// generated route for
/// [_i1.AccountScreen]
class AccountRoute extends _i14.PageRouteInfo<void> {
const AccountRoute({List<_i14.PageRouteInfo>? children})
class AccountRoute extends _i15.PageRouteInfo<void> {
const AccountRoute({List<_i15.PageRouteInfo>? children})
: super(AccountRoute.name, initialChildren: children);
static const String name = 'AccountRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i1.AccountScreen();
@ -43,28 +44,69 @@ class AccountRoute extends _i14.PageRouteInfo<void> {
}
/// generated route for
/// [_i2.ChatListScreen]
class ChatListRoute extends _i14.PageRouteInfo<void> {
const ChatListRoute({List<_i14.PageRouteInfo>? children})
/// [_i2.ChatDetailScreen]
class ChatDetailRoute extends _i15.PageRouteInfo<ChatDetailRouteArgs> {
ChatDetailRoute({
_i16.Key? key,
required int id,
List<_i15.PageRouteInfo>? children,
}) : super(
ChatDetailRoute.name,
args: ChatDetailRouteArgs(key: key, id: id),
rawPathParams: {'id': id},
initialChildren: children,
);
static const String name = 'ChatDetailRoute';
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<ChatDetailRouteArgs>(
orElse: () => ChatDetailRouteArgs(id: pathParams.getInt('id')),
);
return _i2.ChatDetailScreen(key: args.key, id: args.id);
},
);
}
class ChatDetailRouteArgs {
const ChatDetailRouteArgs({this.key, required this.id});
final _i16.Key? key;
final int id;
@override
String toString() {
return 'ChatDetailRouteArgs{key: $key, id: $id}';
}
}
/// generated route for
/// [_i3.ChatListScreen]
class ChatListRoute extends _i15.PageRouteInfo<void> {
const ChatListRoute({List<_i15.PageRouteInfo>? children})
: super(ChatListRoute.name, initialChildren: children);
static const String name = 'ChatListRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i2.ChatListScreen();
return const _i3.ChatListScreen();
},
);
}
/// generated route for
/// [_i3.ChatRoomScreen]
class ChatRoomRoute extends _i14.PageRouteInfo<ChatRoomRouteArgs> {
/// [_i4.ChatRoomScreen]
class ChatRoomRoute extends _i15.PageRouteInfo<ChatRoomRouteArgs> {
ChatRoomRoute({
_i15.Key? key,
_i16.Key? key,
required int id,
List<_i14.PageRouteInfo>? children,
List<_i15.PageRouteInfo>? children,
}) : super(
ChatRoomRoute.name,
args: ChatRoomRouteArgs(key: key, id: id),
@ -74,14 +116,14 @@ class ChatRoomRoute extends _i14.PageRouteInfo<ChatRoomRouteArgs> {
static const String name = 'ChatRoomRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<ChatRoomRouteArgs>(
orElse: () => ChatRoomRouteArgs(id: pathParams.getInt('id')),
);
return _i3.ChatRoomScreen(key: args.key, id: args.id);
return _i4.ChatRoomScreen(key: args.key, id: args.id);
},
);
}
@ -89,7 +131,7 @@ class ChatRoomRoute extends _i14.PageRouteInfo<ChatRoomRouteArgs> {
class ChatRoomRouteArgs {
const ChatRoomRouteArgs({this.key, required this.id});
final _i15.Key? key;
final _i16.Key? key;
final int id;
@ -100,25 +142,25 @@ class ChatRoomRouteArgs {
}
/// generated route for
/// [_i4.CreateAccountScreen]
class CreateAccountRoute extends _i14.PageRouteInfo<void> {
const CreateAccountRoute({List<_i14.PageRouteInfo>? children})
/// [_i5.CreateAccountScreen]
class CreateAccountRoute extends _i15.PageRouteInfo<void> {
const CreateAccountRoute({List<_i15.PageRouteInfo>? children})
: super(CreateAccountRoute.name, initialChildren: children);
static const String name = 'CreateAccountRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i4.CreateAccountScreen();
return const _i5.CreateAccountScreen();
},
);
}
/// generated route for
/// [_i2.EditChatScreen]
class EditChatRoute extends _i14.PageRouteInfo<EditChatRouteArgs> {
EditChatRoute({_i15.Key? key, int? id, List<_i14.PageRouteInfo>? children})
/// [_i3.EditChatScreen]
class EditChatRoute extends _i15.PageRouteInfo<EditChatRouteArgs> {
EditChatRoute({_i16.Key? key, int? id, List<_i15.PageRouteInfo>? children})
: super(
EditChatRoute.name,
args: EditChatRouteArgs(key: key, id: id),
@ -128,14 +170,14 @@ class EditChatRoute extends _i14.PageRouteInfo<EditChatRouteArgs> {
static const String name = 'EditChatRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<EditChatRouteArgs>(
orElse: () => EditChatRouteArgs(id: pathParams.optInt('id')),
);
return _i2.EditChatScreen(key: args.key, id: args.id);
return _i3.EditChatScreen(key: args.key, id: args.id);
},
);
}
@ -143,7 +185,7 @@ class EditChatRoute extends _i14.PageRouteInfo<EditChatRouteArgs> {
class EditChatRouteArgs {
const EditChatRouteArgs({this.key, this.id});
final _i15.Key? key;
final _i16.Key? key;
final int? id;
@ -154,12 +196,12 @@ class EditChatRouteArgs {
}
/// generated route for
/// [_i5.EditPublisherScreen]
class EditPublisherRoute extends _i14.PageRouteInfo<EditPublisherRouteArgs> {
/// [_i6.EditPublisherScreen]
class EditPublisherRoute extends _i15.PageRouteInfo<EditPublisherRouteArgs> {
EditPublisherRoute({
_i15.Key? key,
_i16.Key? key,
String? name,
List<_i14.PageRouteInfo>? children,
List<_i15.PageRouteInfo>? children,
}) : super(
EditPublisherRoute.name,
args: EditPublisherRouteArgs(key: key, name: name),
@ -169,14 +211,14 @@ class EditPublisherRoute extends _i14.PageRouteInfo<EditPublisherRouteArgs> {
static const String name = 'EditPublisherRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<EditPublisherRouteArgs>(
orElse: () => EditPublisherRouteArgs(name: pathParams.optString('id')),
);
return _i5.EditPublisherScreen(key: args.key, name: args.name);
return _i6.EditPublisherScreen(key: args.key, name: args.name);
},
);
}
@ -184,7 +226,7 @@ class EditPublisherRoute extends _i14.PageRouteInfo<EditPublisherRouteArgs> {
class EditPublisherRouteArgs {
const EditPublisherRouteArgs({this.key, this.name});
final _i15.Key? key;
final _i16.Key? key;
final String? name;
@ -195,12 +237,12 @@ class EditPublisherRouteArgs {
}
/// generated route for
/// [_i6.EditRealmScreen]
class EditRealmRoute extends _i14.PageRouteInfo<EditRealmRouteArgs> {
/// [_i7.EditRealmScreen]
class EditRealmRoute extends _i15.PageRouteInfo<EditRealmRouteArgs> {
EditRealmRoute({
_i15.Key? key,
_i16.Key? key,
String? slug,
List<_i14.PageRouteInfo>? children,
List<_i15.PageRouteInfo>? children,
}) : super(
EditRealmRoute.name,
args: EditRealmRouteArgs(key: key, slug: slug),
@ -210,14 +252,14 @@ class EditRealmRoute extends _i14.PageRouteInfo<EditRealmRouteArgs> {
static const String name = 'EditRealmRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<EditRealmRouteArgs>(
orElse: () => EditRealmRouteArgs(slug: pathParams.optString('slug')),
);
return _i6.EditRealmScreen(key: args.key, slug: args.slug);
return _i7.EditRealmScreen(key: args.key, slug: args.slug);
},
);
}
@ -225,7 +267,7 @@ class EditRealmRoute extends _i14.PageRouteInfo<EditRealmRouteArgs> {
class EditRealmRouteArgs {
const EditRealmRouteArgs({this.key, this.slug});
final _i15.Key? key;
final _i16.Key? key;
final String? slug;
@ -236,124 +278,124 @@ class EditRealmRouteArgs {
}
/// generated route for
/// [_i7.ExploreScreen]
class ExploreRoute extends _i14.PageRouteInfo<void> {
const ExploreRoute({List<_i14.PageRouteInfo>? children})
/// [_i8.ExploreScreen]
class ExploreRoute extends _i15.PageRouteInfo<void> {
const ExploreRoute({List<_i15.PageRouteInfo>? children})
: super(ExploreRoute.name, initialChildren: children);
static const String name = 'ExploreRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i7.ExploreScreen();
return const _i8.ExploreScreen();
},
);
}
/// generated route for
/// [_i8.LoginScreen]
class LoginRoute extends _i14.PageRouteInfo<void> {
const LoginRoute({List<_i14.PageRouteInfo>? children})
/// [_i9.LoginScreen]
class LoginRoute extends _i15.PageRouteInfo<void> {
const LoginRoute({List<_i15.PageRouteInfo>? children})
: super(LoginRoute.name, initialChildren: children);
static const String name = 'LoginRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i8.LoginScreen();
return const _i9.LoginScreen();
},
);
}
/// generated route for
/// [_i5.ManagedPublisherScreen]
class ManagedPublisherRoute extends _i14.PageRouteInfo<void> {
const ManagedPublisherRoute({List<_i14.PageRouteInfo>? children})
/// [_i6.ManagedPublisherScreen]
class ManagedPublisherRoute extends _i15.PageRouteInfo<void> {
const ManagedPublisherRoute({List<_i15.PageRouteInfo>? children})
: super(ManagedPublisherRoute.name, initialChildren: children);
static const String name = 'ManagedPublisherRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i5.ManagedPublisherScreen();
return const _i6.ManagedPublisherScreen();
},
);
}
/// generated route for
/// [_i9.MyselfProfileScreen]
class MyselfProfileRoute extends _i14.PageRouteInfo<void> {
const MyselfProfileRoute({List<_i14.PageRouteInfo>? children})
/// [_i10.MyselfProfileScreen]
class MyselfProfileRoute extends _i15.PageRouteInfo<void> {
const MyselfProfileRoute({List<_i15.PageRouteInfo>? children})
: super(MyselfProfileRoute.name, initialChildren: children);
static const String name = 'MyselfProfileRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i9.MyselfProfileScreen();
return const _i10.MyselfProfileScreen();
},
);
}
/// generated route for
/// [_i2.NewChatScreen]
class NewChatRoute extends _i14.PageRouteInfo<void> {
const NewChatRoute({List<_i14.PageRouteInfo>? children})
/// [_i3.NewChatScreen]
class NewChatRoute extends _i15.PageRouteInfo<void> {
const NewChatRoute({List<_i15.PageRouteInfo>? children})
: super(NewChatRoute.name, initialChildren: children);
static const String name = 'NewChatRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i2.NewChatScreen();
return const _i3.NewChatScreen();
},
);
}
/// generated route for
/// [_i5.NewPublisherScreen]
class NewPublisherRoute extends _i14.PageRouteInfo<void> {
const NewPublisherRoute({List<_i14.PageRouteInfo>? children})
/// [_i6.NewPublisherScreen]
class NewPublisherRoute extends _i15.PageRouteInfo<void> {
const NewPublisherRoute({List<_i15.PageRouteInfo>? children})
: super(NewPublisherRoute.name, initialChildren: children);
static const String name = 'NewPublisherRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i5.NewPublisherScreen();
return const _i6.NewPublisherScreen();
},
);
}
/// generated route for
/// [_i6.NewRealmScreen]
class NewRealmRoute extends _i14.PageRouteInfo<void> {
const NewRealmRoute({List<_i14.PageRouteInfo>? children})
/// [_i7.NewRealmScreen]
class NewRealmRoute extends _i15.PageRouteInfo<void> {
const NewRealmRoute({List<_i15.PageRouteInfo>? children})
: super(NewRealmRoute.name, initialChildren: children);
static const String name = 'NewRealmRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i6.NewRealmScreen();
return const _i7.NewRealmScreen();
},
);
}
/// generated route for
/// [_i10.PostComposeScreen]
class PostComposeRoute extends _i14.PageRouteInfo<PostComposeRouteArgs> {
/// [_i11.PostComposeScreen]
class PostComposeRoute extends _i15.PageRouteInfo<PostComposeRouteArgs> {
PostComposeRoute({
_i15.Key? key,
_i16.SnPost? originalPost,
List<_i14.PageRouteInfo>? children,
_i16.Key? key,
_i17.SnPost? originalPost,
List<_i15.PageRouteInfo>? children,
}) : super(
PostComposeRoute.name,
args: PostComposeRouteArgs(key: key, originalPost: originalPost),
@ -362,13 +404,13 @@ class PostComposeRoute extends _i14.PageRouteInfo<PostComposeRouteArgs> {
static const String name = 'PostComposeRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final args = data.argsAs<PostComposeRouteArgs>(
orElse: () => const PostComposeRouteArgs(),
);
return _i10.PostComposeScreen(
return _i11.PostComposeScreen(
key: args.key,
originalPost: args.originalPost,
);
@ -379,9 +421,9 @@ class PostComposeRoute extends _i14.PageRouteInfo<PostComposeRouteArgs> {
class PostComposeRouteArgs {
const PostComposeRouteArgs({this.key, this.originalPost});
final _i15.Key? key;
final _i16.Key? key;
final _i16.SnPost? originalPost;
final _i17.SnPost? originalPost;
@override
String toString() {
@ -390,12 +432,12 @@ class PostComposeRouteArgs {
}
/// generated route for
/// [_i11.PostDetailScreen]
class PostDetailRoute extends _i14.PageRouteInfo<PostDetailRouteArgs> {
/// [_i12.PostDetailScreen]
class PostDetailRoute extends _i15.PageRouteInfo<PostDetailRouteArgs> {
PostDetailRoute({
_i15.Key? key,
_i16.Key? key,
required int id,
List<_i14.PageRouteInfo>? children,
List<_i15.PageRouteInfo>? children,
}) : super(
PostDetailRoute.name,
args: PostDetailRouteArgs(key: key, id: id),
@ -405,14 +447,14 @@ class PostDetailRoute extends _i14.PageRouteInfo<PostDetailRouteArgs> {
static const String name = 'PostDetailRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<PostDetailRouteArgs>(
orElse: () => PostDetailRouteArgs(id: pathParams.getInt('id')),
);
return _i11.PostDetailScreen(key: args.key, id: args.id);
return _i12.PostDetailScreen(key: args.key, id: args.id);
},
);
}
@ -420,7 +462,7 @@ class PostDetailRoute extends _i14.PageRouteInfo<PostDetailRouteArgs> {
class PostDetailRouteArgs {
const PostDetailRouteArgs({this.key, required this.id});
final _i15.Key? key;
final _i16.Key? key;
final int id;
@ -431,12 +473,12 @@ class PostDetailRouteArgs {
}
/// generated route for
/// [_i10.PostEditScreen]
class PostEditRoute extends _i14.PageRouteInfo<PostEditRouteArgs> {
/// [_i11.PostEditScreen]
class PostEditRoute extends _i15.PageRouteInfo<PostEditRouteArgs> {
PostEditRoute({
_i15.Key? key,
_i16.Key? key,
required int id,
List<_i14.PageRouteInfo>? children,
List<_i15.PageRouteInfo>? children,
}) : super(
PostEditRoute.name,
args: PostEditRouteArgs(key: key, id: id),
@ -446,14 +488,14 @@ class PostEditRoute extends _i14.PageRouteInfo<PostEditRouteArgs> {
static const String name = 'PostEditRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
final pathParams = data.inheritedPathParams;
final args = data.argsAs<PostEditRouteArgs>(
orElse: () => PostEditRouteArgs(id: pathParams.getInt('id')),
);
return _i10.PostEditScreen(key: args.key, id: args.id);
return _i11.PostEditScreen(key: args.key, id: args.id);
},
);
}
@ -461,7 +503,7 @@ class PostEditRoute extends _i14.PageRouteInfo<PostEditRouteArgs> {
class PostEditRouteArgs {
const PostEditRouteArgs({this.key, required this.id});
final _i15.Key? key;
final _i16.Key? key;
final int id;
@ -472,49 +514,49 @@ class PostEditRouteArgs {
}
/// generated route for
/// [_i6.RealmListScreen]
class RealmListRoute extends _i14.PageRouteInfo<void> {
const RealmListRoute({List<_i14.PageRouteInfo>? children})
/// [_i7.RealmListScreen]
class RealmListRoute extends _i15.PageRouteInfo<void> {
const RealmListRoute({List<_i15.PageRouteInfo>? children})
: super(RealmListRoute.name, initialChildren: children);
static const String name = 'RealmListRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i6.RealmListScreen();
return const _i7.RealmListScreen();
},
);
}
/// generated route for
/// [_i12.TabsScreen]
class TabsRoute extends _i14.PageRouteInfo<void> {
const TabsRoute({List<_i14.PageRouteInfo>? children})
/// [_i13.TabsScreen]
class TabsRoute extends _i15.PageRouteInfo<void> {
const TabsRoute({List<_i15.PageRouteInfo>? children})
: super(TabsRoute.name, initialChildren: children);
static const String name = 'TabsRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i12.TabsScreen();
return const _i13.TabsScreen();
},
);
}
/// generated route for
/// [_i13.UpdateProfileScreen]
class UpdateProfileRoute extends _i14.PageRouteInfo<void> {
const UpdateProfileRoute({List<_i14.PageRouteInfo>? children})
/// [_i14.UpdateProfileScreen]
class UpdateProfileRoute extends _i15.PageRouteInfo<void> {
const UpdateProfileRoute({List<_i15.PageRouteInfo>? children})
: super(UpdateProfileRoute.name, initialChildren: children);
static const String name = 'UpdateProfileRoute';
static _i14.PageInfo page = _i14.PageInfo(
static _i15.PageInfo page = _i15.PageInfo(
name,
builder: (data) {
return const _i13.UpdateProfileScreen();
return const _i14.UpdateProfileScreen();
},
);
}

View File

@ -22,7 +22,9 @@ class AccountScreen extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userInfoProvider);
if (!user.hasValue) return _UnauthorizedAccountScreen();
if (!user.hasValue || user.value == null) {
return _UnauthorizedAccountScreen();
}
return AppScaffold(
appBar: AppBar(title: const Text('Account')),
@ -52,7 +54,7 @@ class AccountScreen extends HookConsumerWidget {
spacing: 16,
children: [
ProfilePictureWidget(
fileId: user.value!.profile.pictureId,
fileId: user.value?.profile.pictureId,
radius: 24,
),
Column(

View File

@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:island/models/chat.dart';
@ -15,6 +16,7 @@ import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
@ -36,9 +38,23 @@ class ChatListScreen extends HookConsumerWidget {
final chats = ref.watch(chatroomsJoinedProvider);
return AppScaffold(
appBar: AppBar(title: Text('chat').tr()),
appBar: AppBar(
title: Text('chat').tr(),
actions: [
IconButton(
icon: const Icon(Symbols.email),
onPressed: () {
showCupertinoModalBottomSheet(
context: context,
builder: (context) => _ChatInvitesSheet(),
);
},
),
const Gap(8),
],
),
floatingActionButton: FloatingActionButton(
key: Key("chat-page-fab"),
heroTag: Key("chat-page-fab"),
onPressed: () {
context.pushRoute(NewChatRoute());
},
@ -58,7 +74,7 @@ class ChatListScreen extends HookConsumerWidget {
final item = items[index];
return ListTile(
leading:
item.picture == null
item.pictureId == null
? CircleAvatar(
child: Text(item.name[0].toUpperCase()),
)
@ -87,6 +103,14 @@ Future<SnChat?> chatroom(Ref ref, int? identifier) async {
return SnChat.fromJson(resp.data);
}
@riverpod
Future<SnChatMember?> chatroomIdentity(Ref ref, int? identifier) async {
if (identifier == null) return null;
final client = ref.watch(apiClientProvider);
final resp = await client.get('/chat/$identifier/members/me');
return SnChatMember.fromJson(resp.data);
}
@RoutePage()
class NewChatScreen extends StatelessWidget {
const NewChatScreen({super.key});
@ -275,3 +299,146 @@ class EditChatScreen extends HookConsumerWidget {
);
}
}
@riverpod
Future<List<SnChatMember>> chatroomInvites(Ref ref) async {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/chat/invites');
return resp.data
.map((e) => SnChatMember.fromJson(e))
.cast<SnChatMember>()
.toList();
}
class _ChatInvitesSheet extends HookConsumerWidget {
const _ChatInvitesSheet();
@override
Widget build(BuildContext context, WidgetRef ref) {
final invites = ref.watch(chatroomInvitesProvider);
Future<void> acceptInvite(SnChatMember invite) async {
try {
final client = ref.read(apiClientProvider);
await client.post('/chat/invites/${invite.chatRoom!.id}/accept');
ref.invalidate(chatroomInvitesProvider);
ref.invalidate(chatroomsJoinedProvider);
} catch (err) {
showErrorAlert(err);
}
}
Future<void> declineInvite(SnChatMember invite) async {
try {
final client = ref.read(apiClientProvider);
await client.post('/chat/invites/${invite.chatRoom!.id}/decline');
ref.invalidate(chatroomInvitesProvider);
} catch (err) {
showErrorAlert(err);
}
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
child: Material(
color: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
top: 16,
left: 20,
right: 16,
bottom: 12,
),
child: Row(
children: [
Text(
'invites'.tr(),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
),
),
const Spacer(),
IconButton(
icon: const Icon(Symbols.refresh),
style: IconButton.styleFrom(
minimumSize: const Size(36, 36),
),
onPressed: () {
ref.refresh(chatroomInvitesProvider.future);
},
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => Navigator.pop(context),
style: IconButton.styleFrom(
minimumSize: const Size(36, 36),
),
),
],
),
),
const Divider(height: 1),
Expanded(
child: invites.when(
data:
(items) =>
items.isEmpty
? Center(
child:
Text(
'invitesEmpty',
textAlign: TextAlign.center,
).tr(),
)
: ListView.builder(
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (context, index) {
final invite = items[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: invite.chatRoom!.pictureId,
radius: 24,
fallbackIcon: Symbols.group,
),
title: Text(invite.chatRoom!.name),
subtitle:
Text(
invite.role >= 100
? 'permissionOwner'
: invite.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Symbols.check),
onPressed: () => acceptInvite(invite),
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => declineInvite(invite),
),
],
),
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
),
],
),
),
);
}
}

View File

@ -163,5 +163,146 @@ class _ChatroomProviderElement extends AutoDisposeFutureProviderElement<SnChat?>
int? get identifier => (origin as ChatroomProvider).identifier;
}
String _$chatroomIdentityHash() => r'b20322591279d0336f2f309729e7e0cb9809063f';
/// See also [chatroomIdentity].
@ProviderFor(chatroomIdentity)
const chatroomIdentityProvider = ChatroomIdentityFamily();
/// See also [chatroomIdentity].
class ChatroomIdentityFamily extends Family<AsyncValue<SnChatMember?>> {
/// See also [chatroomIdentity].
const ChatroomIdentityFamily();
/// See also [chatroomIdentity].
ChatroomIdentityProvider call(int? identifier) {
return ChatroomIdentityProvider(identifier);
}
@override
ChatroomIdentityProvider getProviderOverride(
covariant ChatroomIdentityProvider provider,
) {
return call(provider.identifier);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'chatroomIdentityProvider';
}
/// See also [chatroomIdentity].
class ChatroomIdentityProvider
extends AutoDisposeFutureProvider<SnChatMember?> {
/// See also [chatroomIdentity].
ChatroomIdentityProvider(int? identifier)
: this._internal(
(ref) => chatroomIdentity(ref as ChatroomIdentityRef, identifier),
from: chatroomIdentityProvider,
name: r'chatroomIdentityProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomIdentityHash,
dependencies: ChatroomIdentityFamily._dependencies,
allTransitiveDependencies:
ChatroomIdentityFamily._allTransitiveDependencies,
identifier: identifier,
);
ChatroomIdentityProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.identifier,
}) : super.internal();
final int? identifier;
@override
Override overrideWith(
FutureOr<SnChatMember?> Function(ChatroomIdentityRef provider) create,
) {
return ProviderOverride(
origin: this,
override: ChatroomIdentityProvider._internal(
(ref) => create(ref as ChatroomIdentityRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
identifier: identifier,
),
);
}
@override
AutoDisposeFutureProviderElement<SnChatMember?> createElement() {
return _ChatroomIdentityProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ChatroomIdentityProvider && other.identifier == identifier;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, identifier.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ChatroomIdentityRef on AutoDisposeFutureProviderRef<SnChatMember?> {
/// The parameter `identifier` of this provider.
int? get identifier;
}
class _ChatroomIdentityProviderElement
extends AutoDisposeFutureProviderElement<SnChatMember?>
with ChatroomIdentityRef {
_ChatroomIdentityProviderElement(super.provider);
@override
int? get identifier => (origin as ChatroomIdentityProvider).identifier;
}
String _$chatroomInvitesHash() => r'c15f06c1e9c6074e6159d9d1f4404f31250ce523';
/// See also [chatroomInvites].
@ProviderFor(chatroomInvites)
final chatroomInvitesProvider =
AutoDisposeFutureProvider<List<SnChatMember>>.internal(
chatroomInvites,
name: r'chatroomInvitesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomInvitesHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ChatroomInvitesRef = AutoDisposeFutureProviderRef<List<SnChatMember>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -1,4 +1,4 @@
import 'package:auto_route/annotations.dart';
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -9,6 +9,7 @@ import 'package:island/database/message.dart';
import 'package:island/database/message_repository.dart';
import 'package:island/pods/message.dart';
import 'package:island/pods/network.dart';
import 'package:island/route.gr.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
@ -19,9 +20,10 @@ import 'chat.dart';
final messageRepositoryProvider = FutureProvider.family<MessageRepository, int>(
(ref, roomId) async {
final room = await ref.watch(chatroomProvider(roomId).future);
final identity = await ref.watch(chatroomIdentityProvider(roomId).future);
final apiClient = ref.watch(apiClientProvider);
final database = ref.watch(databaseProvider);
return MessageRepository(room!, apiClient, database);
return MessageRepository(room!, identity!, apiClient, database);
},
);
@ -153,8 +155,10 @@ class ChatRoomScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final chatRoom = ref.watch(chatroomProvider(id));
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
final messages = ref.watch(messagesProvider(id));
final messagesNotifier = ref.read(messagesProvider(id).notifier);
final messagesRepo = ref.watch(messageRepositoryProvider(id));
final messageController = useTextEditingController();
final scrollController = useScrollController();
@ -185,7 +189,7 @@ class ChatRoomScreen extends HookConsumerWidget {
height: 26,
width: 26,
child:
room?.picture != null
room?.pictureId != null
? ProfilePictureWidget(
fileId: room?.pictureId,
fallbackIcon: Symbols.chat,
@ -197,14 +201,19 @@ class ChatRoomScreen extends HookConsumerWidget {
),
),
),
Text(room?.name ?? 'unknown').fontSize(19).tr(),
Text(room?.name ?? 'unknown'.tr()).fontSize(19),
],
),
loading: () => const Text('Loading...'),
error: (_, __) => const Text('Error'),
),
actions: [
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {
context.router.push(ChatDetailRoute(id: id));
},
),
const Gap(8),
],
),
@ -217,12 +226,27 @@ class ChatRoomScreen extends HookConsumerWidget {
messageList.isEmpty
? Center(child: Text('No messages yet'.tr()))
: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 16),
controller: scrollController,
reverse: true, // Show newest messages at the bottom
itemCount: messageList.length,
itemBuilder: (context, index) {
final message = messageList[index];
return MessageBubble(message: message);
return chatIdentity.when(
skipError: true,
data:
(identity) => MessageBubble(
message: message,
isCurrentUser:
identity?.id == message.senderId,
),
loading:
() => MessageBubble(
message: message,
isCurrentUser: false,
),
error: (_, __) => const SizedBox.shrink(),
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
@ -302,14 +326,16 @@ class ChatRoomScreen extends HookConsumerWidget {
class MessageBubble extends StatelessWidget {
final LocalChatMessage message;
final bool isCurrentUser;
const MessageBubble({Key? key, required this.message}) : super(key: key);
const MessageBubble({
super.key,
required this.message,
required this.isCurrentUser,
});
@override
Widget build(BuildContext context) {
final isCurrentUser =
message.senderId == 'current_user_id'; // Replace with actual check
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Row(
@ -365,7 +391,11 @@ class MessageBubble extends StatelessWidget {
),
const Gap(8),
if (isCurrentUser)
const SizedBox(width: 32), // Balance with avatar on the other side
ProfilePictureWidget(
fileId:
message.toRemoteMessage().sender.account.profile.pictureId,
radius: 16,
),
],
),
);

View File

@ -0,0 +1,382 @@
import 'package:auto_route/auto_route.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/chat.dart';
import 'package:island/pods/network.dart';
import 'package:island/route.gr.dart';
import 'package:island/screens/chat/chat.dart';
import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:styled_widget/styled_widget.dart';
part 'room_detail.freezed.dart';
@RoutePage()
class ChatDetailScreen extends HookConsumerWidget {
final int id;
const ChatDetailScreen({super.key, @PathParam("id") required this.id});
@override
Widget build(BuildContext context, WidgetRef ref) {
final roomState = ref.watch(chatroomProvider(id));
final roomIdentity = ref.watch(chatroomIdentityProvider(id));
final isModerator = roomIdentity.when(
loading: () => false,
error: (error, _) => false,
data: (identity) => (identity?.role ?? 0) >= 50,
);
const iconShadow = Shadow(
color: Colors.black54,
blurRadius: 5.0,
offset: Offset(1.0, 1.0),
);
return Scaffold(
body: roomState.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')),
data:
(currentRoom) => CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 180,
pinned: true,
leading: PageBackButton(shadows: [iconShadow]),
flexibleSpace: FlexibleSpaceBar(
background:
currentRoom?.backgroundId != null
? CloudImageWidget(
fileId: currentRoom!.backgroundId!,
fit: BoxFit.cover,
)
: Container(
color:
Theme.of(context).appBarTheme.backgroundColor,
),
title: Text(
currentRoom?.name ?? 'unknown'.tr(),
).textColor(Theme.of(context).appBarTheme.foregroundColor),
),
actions: [
IconButton(
icon: const Icon(Icons.people, shadows: [iconShadow]),
onPressed: () {
showCupertinoModalBottomSheet(
context: context,
builder:
(context) => _ChatMemberListSheet(roomId: id),
);
},
),
if (isModerator)
_ChatRoomActionMenu(id: id, iconShadow: iconShadow),
const Gap(8),
],
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentRoom?.description ?? 'descriptionNone'.tr(),
style: const TextStyle(fontSize: 16),
),
],
),
),
),
],
),
),
);
}
}
class _ChatRoomActionMenu extends StatelessWidget {
final int id;
final Shadow iconShadow;
const _ChatRoomActionMenu({required this.id, required this.iconShadow});
@override
Widget build(BuildContext context) {
return PopupMenuButton(
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
itemBuilder:
(context) => [
PopupMenuItem(
onTap: () {
context.router.replace(EditChatRoute(id: id));
},
child: Row(
children: [
Icon(
Icons.edit,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
const Gap(12),
const Text('editChatRoom').tr(),
],
),
),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.delete, color: Colors.red),
const Gap(12),
const Text(
'deleteChatRoom',
style: TextStyle(color: Colors.red),
).tr(),
],
),
onTap: () {
Navigator.pop(context);
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: const Text('Delete Room'),
content: const Text(
'Are you sure you want to delete this room? This action cannot be undone.',
),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: () => Navigator.pop(context),
),
TextButton(
child: const Text(
'Delete',
style: TextStyle(color: Colors.red),
),
onPressed: () async {},
),
],
),
);
},
),
],
);
}
}
@freezed
abstract class ChatRoomMemberState with _$ChatRoomMemberState {
const factory ChatRoomMemberState({
required List<SnChatMember> members,
required bool isLoading,
required int total,
String? error,
}) = _ChatRoomMemberState;
}
final chatMemberStateProvider =
StateNotifierProvider.family<ChatMemberNotifier, ChatRoomMemberState, int>((
ref,
roomId,
) {
final apiClient = ref.watch(apiClientProvider);
return ChatMemberNotifier(apiClient, roomId);
});
class ChatMemberNotifier extends StateNotifier<ChatRoomMemberState> {
final int roomId;
final Dio _apiClient;
ChatMemberNotifier(this._apiClient, this.roomId)
: super(const ChatRoomMemberState(members: [], isLoading: false, total: 0));
Future<void> loadMore({int offset = 0, int take = 20}) async {
if (state.isLoading) return;
if (state.total > 0 && state.members.length >= state.total) return;
state = state.copyWith(isLoading: true, error: null);
try {
final response = await _apiClient.get(
'/chat/$roomId/members',
queryParameters: {'offset': offset, 'take': take},
);
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data;
final members = data.map((e) => SnChatMember.fromJson(e)).toList();
state = state.copyWith(
members: [...state.members, ...members],
total: total,
isLoading: false,
);
} catch (e) {
state = state.copyWith(error: e.toString(), isLoading: false);
}
}
void reset() {
state = const ChatRoomMemberState(members: [], isLoading: false, total: 0);
}
}
class _ChatMemberListSheet extends HookConsumerWidget {
final int roomId;
const _ChatMemberListSheet({required this.roomId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final memberState = ref.watch(chatMemberStateProvider(roomId));
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
useEffect(() {
Future(() {
memberNotifier.loadMore();
});
return null;
}, []);
Future<void> invitePerson() async {
final result = await showCupertinoModalBottomSheet(
context: context,
builder: (context) => const AccountPickerSheet(),
);
if (result == null) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.post(
'/chat/invites/$roomId',
data: {'related_user_id': result.id, 'role': 0},
);
memberNotifier.reset();
await memberNotifier.loadMore();
} catch (err) {
showErrorAlert(err);
}
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
child: Material(
color: Colors.transparent,
child: Column(
children: [
Padding(
padding: EdgeInsets.only(
top: 16,
left: 20,
right: 16,
bottom: 12,
),
child: Row(
children: [
Text(
'chatMembers'.plural(memberState.total),
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: -0.5,
),
),
const Spacer(),
IconButton(
icon: const Icon(Symbols.person_add),
onPressed: invitePerson,
style: IconButton.styleFrom(
minimumSize: const Size(36, 36),
),
),
IconButton(
icon: const Icon(Symbols.refresh),
onPressed: () {
memberNotifier.reset();
memberNotifier.loadMore();
},
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => Navigator.pop(context),
style: IconButton.styleFrom(
minimumSize: const Size(36, 36),
),
),
],
),
),
const Divider(height: 1),
Expanded(
child:
memberState.error != null
? Center(child: Text(memberState.error!))
: ListView.builder(
itemCount: memberState.members.length + 1,
itemBuilder: (context, index) {
if (index == memberState.members.length) {
if (memberState.isLoading) {
return const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
),
);
}
if (memberState.members.length <
memberState.total) {
memberNotifier.loadMore(
offset: memberState.members.length,
);
}
return const SizedBox.shrink();
}
final member = memberState.members[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: member.account.profile.pictureId,
),
title: Row(
spacing: 6,
children: [
Flexible(child: Text(member.account.nick)),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],
),
subtitle: Row(
children: [
Text(
member.role >= 100
? 'permissionOwner'
: member.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
Text('·').bold().padding(horizontal: 6),
Expanded(
child: Text("@${member.account.name}"),
),
],
),
);
},
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,157 @@
// dart format width=80
// 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 'room_detail.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ChatRoomMemberState {
List<SnChatMember> get members; bool get isLoading; int get total; String? get error;
/// Create a copy of ChatRoomMemberState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ChatRoomMemberStateCopyWith<ChatRoomMemberState> get copyWith => _$ChatRoomMemberStateCopyWithImpl<ChatRoomMemberState>(this as ChatRoomMemberState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChatRoomMemberState&&const DeepCollectionEquality().equals(other.members, members)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.total, total) || other.total == total)&&(identical(other.error, error) || other.error == error));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(members),isLoading,total,error);
@override
String toString() {
return 'ChatRoomMemberState(members: $members, isLoading: $isLoading, total: $total, error: $error)';
}
}
/// @nodoc
abstract mixin class $ChatRoomMemberStateCopyWith<$Res> {
factory $ChatRoomMemberStateCopyWith(ChatRoomMemberState value, $Res Function(ChatRoomMemberState) _then) = _$ChatRoomMemberStateCopyWithImpl;
@useResult
$Res call({
List<SnChatMember> members, bool isLoading, int total, String? error
});
}
/// @nodoc
class _$ChatRoomMemberStateCopyWithImpl<$Res>
implements $ChatRoomMemberStateCopyWith<$Res> {
_$ChatRoomMemberStateCopyWithImpl(this._self, this._then);
final ChatRoomMemberState _self;
final $Res Function(ChatRoomMemberState) _then;
/// Create a copy of ChatRoomMemberState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? members = null,Object? isLoading = null,Object? total = null,Object? error = freezed,}) {
return _then(_self.copyWith(
members: null == members ? _self.members : members // ignore: cast_nullable_to_non_nullable
as List<SnChatMember>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
as int,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class _ChatRoomMemberState implements ChatRoomMemberState {
const _ChatRoomMemberState({required final List<SnChatMember> members, required this.isLoading, required this.total, this.error}): _members = members;
final List<SnChatMember> _members;
@override List<SnChatMember> get members {
if (_members is EqualUnmodifiableListView) return _members;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_members);
}
@override final bool isLoading;
@override final int total;
@override final String? error;
/// Create a copy of ChatRoomMemberState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ChatRoomMemberStateCopyWith<_ChatRoomMemberState> get copyWith => __$ChatRoomMemberStateCopyWithImpl<_ChatRoomMemberState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChatRoomMemberState&&const DeepCollectionEquality().equals(other._members, _members)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.total, total) || other.total == total)&&(identical(other.error, error) || other.error == error));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_members),isLoading,total,error);
@override
String toString() {
return 'ChatRoomMemberState(members: $members, isLoading: $isLoading, total: $total, error: $error)';
}
}
/// @nodoc
abstract mixin class _$ChatRoomMemberStateCopyWith<$Res> implements $ChatRoomMemberStateCopyWith<$Res> {
factory _$ChatRoomMemberStateCopyWith(_ChatRoomMemberState value, $Res Function(_ChatRoomMemberState) _then) = __$ChatRoomMemberStateCopyWithImpl;
@override @useResult
$Res call({
List<SnChatMember> members, bool isLoading, int total, String? error
});
}
/// @nodoc
class __$ChatRoomMemberStateCopyWithImpl<$Res>
implements _$ChatRoomMemberStateCopyWith<$Res> {
__$ChatRoomMemberStateCopyWithImpl(this._self, this._then);
final _ChatRoomMemberState _self;
final $Res Function(_ChatRoomMemberState) _then;
/// Create a copy of ChatRoomMemberState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? members = null,Object? isLoading = null,Object? total = null,Object? error = freezed,}) {
return _then(_ChatRoomMemberState(
members: null == members ? _self._members : members // ignore: cast_nullable_to_non_nullable
as List<SnChatMember>,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
as int,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@ -21,7 +21,7 @@ class ExploreScreen extends ConsumerWidget {
return AppScaffold(
appBar: AppBar(title: const Text('Explore')),
floatingActionButton: FloatingActionButton(
key: Key("explore-page-fab"),
heroTag: Key("explore-page-fab"),
onPressed: () {
context.router.push(PostComposeRoute()).then((value) {
if (value != null) {

View File

@ -39,7 +39,7 @@ class RealmListScreen extends HookConsumerWidget {
return AppScaffold(
appBar: AppBar(title: const Text('realms').tr()),
floatingActionButton: FloatingActionButton(
key: Key("realms-page-fab"),
heroTag: Key("realms-page-fab"),
child: const Icon(Symbols.add),
onPressed: () {
context.router.push(NewRealmRoute());

View File

@ -0,0 +1,107 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/user.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'account_picker.g.dart';
@riverpod
Future<List<SnAccount>> searchAccounts(Ref ref, {required String query}) async {
if (query.isEmpty) {
return [];
}
final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get(
'/accounts/search',
queryParameters: {'query': query},
);
return response.data!
.map((json) => SnAccount.fromJson(json))
.cast<SnAccount>()
.toList();
}
class AccountPickerSheet extends HookConsumerWidget {
const AccountPickerSheet({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final searchController = useTextEditingController();
final debounceTimer = useState<Timer?>(null);
void onSearchChanged(String query) {
debounceTimer.value?.cancel();
debounceTimer.value = Timer(const Duration(milliseconds: 300), () {
ref.read(searchAccountsProvider(query: query));
});
}
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.4,
),
child: Material(
color: Colors.transparent,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 4),
child: TextField(
controller: searchController,
onChanged: onSearchChanged,
decoration: const InputDecoration(
hintText: 'Search accounts...',
contentPadding: EdgeInsets.symmetric(
horizontal: 18,
vertical: 16,
),
),
autofocus: true,
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
),
Expanded(
child: Consumer(
builder: (context, ref, child) {
final searchResult = ref.watch(
searchAccountsProvider(query: searchController.text),
);
return searchResult.when(
data:
(accounts) => ListView.builder(
itemCount: accounts.length,
itemBuilder: (context, index) {
final account = accounts[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: account.profile.pictureId,
),
title: Text(account.nick),
subtitle: Text('@${account.name}'),
onTap: () => Navigator.of(context).pop(account),
);
},
),
loading:
() => const Center(child: CircularProgressIndicator()),
error:
(error, stack) => Center(child: Text('Error: $error')),
);
},
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,153 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account_picker.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$searchAccountsHash() => r'4923cd06876d04515d95d3c58ee3ea9e05c58e4a';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [searchAccounts].
@ProviderFor(searchAccounts)
const searchAccountsProvider = SearchAccountsFamily();
/// See also [searchAccounts].
class SearchAccountsFamily extends Family<AsyncValue<List<SnAccount>>> {
/// See also [searchAccounts].
const SearchAccountsFamily();
/// See also [searchAccounts].
SearchAccountsProvider call({required String query}) {
return SearchAccountsProvider(query: query);
}
@override
SearchAccountsProvider getProviderOverride(
covariant SearchAccountsProvider provider,
) {
return call(query: provider.query);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'searchAccountsProvider';
}
/// See also [searchAccounts].
class SearchAccountsProvider
extends AutoDisposeFutureProvider<List<SnAccount>> {
/// See also [searchAccounts].
SearchAccountsProvider({required String query})
: this._internal(
(ref) => searchAccounts(ref as SearchAccountsRef, query: query),
from: searchAccountsProvider,
name: r'searchAccountsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$searchAccountsHash,
dependencies: SearchAccountsFamily._dependencies,
allTransitiveDependencies:
SearchAccountsFamily._allTransitiveDependencies,
query: query,
);
SearchAccountsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.query,
}) : super.internal();
final String query;
@override
Override overrideWith(
FutureOr<List<SnAccount>> Function(SearchAccountsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: SearchAccountsProvider._internal(
(ref) => create(ref as SearchAccountsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
query: query,
),
);
}
@override
AutoDisposeFutureProviderElement<List<SnAccount>> createElement() {
return _SearchAccountsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is SearchAccountsProvider && other.query == query;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, query.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin SearchAccountsRef on AutoDisposeFutureProviderRef<List<SnAccount>> {
/// The parameter `query` of this provider.
String get query;
}
class _SearchAccountsProviderElement
extends AutoDisposeFutureProviderElement<List<SnAccount>>
with SearchAccountsRef {
_SearchAccountsProviderElement(super.provider);
@override
String get query => (origin as SearchAccountsProvider).query;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/route.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:path_provider/path_provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -157,14 +158,21 @@ class AppScaffold extends StatelessWidget {
}
class PageBackButton extends StatelessWidget {
const PageBackButton({super.key});
final List<Shadow>? shadows;
const PageBackButton({super.key, this.shadows});
@override
Widget build(BuildContext context) {
return BackButton(
return IconButton(
onPressed: () {
context.router.maybePop();
},
icon: Icon(
(!kIsWeb && (Platform.isMacOS || Platform.isIOS))
? Symbols.arrow_back_ios_new
: Symbols.arrow_back,
shadows: shadows,
),
);
}
}

View File

@ -42,6 +42,30 @@ class CloudFileWidget extends ConsumerWidget {
}
}
class CloudImageWidget extends ConsumerWidget {
final String fileId;
final BoxFit fit;
final double aspectRatio;
final String? blurHash;
const CloudImageWidget({
super.key,
required this.fileId,
this.aspectRatio = 1,
this.fit = BoxFit.cover,
this.blurHash,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final serverUrl = ref.watch(serverUrlProvider);
final uri = '$serverUrl/files/$fileId';
return AspectRatio(
aspectRatio: aspectRatio,
child: UniversalImage(uri: uri, blurHash: blurHash),
);
}
}
class ProfilePictureWidget extends ConsumerWidget {
final String? fileId;
final double radius;

View File

@ -1,4 +1,4 @@
platform :osx, '10.14'
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -1,29 +1,264 @@
PODS:
- bitsdojo_window_macos (0.0.1):
- FlutterMacOS
- device_info_plus (0.0.1):
- FlutterMacOS
- file_picker (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- Firebase/CoreOnly (11.10.0):
- FirebaseCore (~> 11.10.0)
- Firebase/Messaging (11.10.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.10.0)
- firebase_core (3.13.0):
- Firebase/CoreOnly (~> 11.10.0)
- FlutterMacOS
- firebase_messaging (15.2.5):
- Firebase/CoreOnly (~> 11.10.0)
- Firebase/Messaging (~> 11.10.0)
- firebase_core
- FlutterMacOS
- FirebaseCore (11.10.0):
- FirebaseCoreInternal (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.10.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.10.0):
- FirebaseCore (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.10.0):
- FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- flutter_inappwebview_macos (0.0.1):
- FlutterMacOS
- OrderedSet (~> 6.0.3)
- flutter_platform_alert (0.0.1):
- FlutterMacOS
- flutter_udid (0.0.1):
- FlutterMacOS
- SAMKeychain
- FlutterMacOS (1.0.0)
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.0)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- irondash_engine_context (0.0.1):
- FlutterMacOS
- media_kit_libs_macos_video (1.0.4):
- FlutterMacOS
- media_kit_video (0.0.1):
- FlutterMacOS
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- OrderedSet (6.0.3)
- package_info_plus (0.0.1):
- FlutterMacOS
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.0)
- quill_native_bridge_macos (0.0.1):
- FlutterMacOS
- SAMKeychain (1.5.3)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- sqlite3 (3.49.1):
- sqlite3/common (= 3.49.1)
- sqlite3/common (3.49.1)
- sqlite3/dbstatvtab (3.49.1):
- sqlite3/common
- sqlite3/fts5 (3.49.1):
- sqlite3/common
- sqlite3/math (3.49.1):
- sqlite3/common
- sqlite3/perf-threadsafe (3.49.1):
- sqlite3/common
- sqlite3/rtree (3.49.1):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- FlutterMacOS
- sqlite3 (~> 3.49.1)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/math
- sqlite3/perf-threadsafe
- sqlite3/rtree
- super_native_extensions (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- volume_controller (0.0.1):
- FlutterMacOS
- wakelock_plus (0.0.1):
- FlutterMacOS
DEPENDENCIES:
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
- flutter_platform_alert (from `Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos`)
- flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`)
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- quill_native_bridge_macos (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
SPEC REPOS:
trunk:
- Firebase
- FirebaseCore
- FirebaseCoreInternal
- FirebaseInstallations
- FirebaseMessaging
- GoogleDataTransport
- GoogleUtilities
- nanopb
- OrderedSet
- PromisesObjC
- SAMKeychain
- sqlite3
EXTERNAL SOURCES:
bitsdojo_window_macos:
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
firebase_core:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
firebase_messaging:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
flutter_inappwebview_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
flutter_platform_alert:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_platform_alert/macos
flutter_udid:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos
FlutterMacOS:
:path: Flutter/ephemeral
irondash_engine_context:
:path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos
media_kit_libs_macos_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
media_kit_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
quill_native_bridge_macos:
:path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
sqlite3_flutter_libs:
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
super_native_extensions:
:path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
volume_controller:
:path: Flutter/ephemeral/.symlinks/plugins/volume_controller/macos
wakelock_plus:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
SPEC CHECKSUMS:
bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
firebase_core: efd50ad8177dc489af1b9163a560359cf1b30597
firebase_messaging: acf2566068a55d7eb8cddfee5b094754070a5b88
FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3
FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
quill_native_bridge_macos: 2b005cb56902bb740e0cd9620aa399dfac6b4882
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
COCOAPODS: 1.16.2

View File

@ -122,7 +122,6 @@
97CE52E8413D7559BF9A7981 /* Pods-RunnerTests.release.xcconfig */,
30FD87E3D579B3530B39AD6D /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
@ -244,6 +243,7 @@
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
F1E275A871246799FC3019F6 /* [CP] Embed Pods Frameworks */,
8D06F41203F1FD2FDE04DC7F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -387,6 +387,23 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
8D06F41203F1FD2FDE04DC7F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
B2D8214A877A92B7299E734E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -583,6 +600,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
@ -715,6 +733,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -735,6 +754,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};