✨ Chat room details, invitions and members management
This commit is contained in:
		| @@ -33,7 +33,7 @@ | |||||||
|   "managedPublisher": "Managed Publishers", |   "managedPublisher": "Managed Publishers", | ||||||
|   "createPublisher": "Create a Publisher", |   "createPublisher": "Create a Publisher", | ||||||
|   "createPublisherHint": "To create posts, collections, etc.", |   "createPublisherHint": "To create posts, collections, etc.", | ||||||
|   "editPublisher": "Edit a Publisher", |   "editPublisher": "Edit Publisher", | ||||||
|   "syncPublisher": "Use Account Data", |   "syncPublisher": "Use Account Data", | ||||||
|   "create": "Create", |   "create": "Create", | ||||||
|   "edit": "Edit", |   "edit": "Edit", | ||||||
| @@ -62,7 +62,7 @@ | |||||||
|   "realms": "Realms", |   "realms": "Realms", | ||||||
|   "createRealm": "Create a Realm", |   "createRealm": "Create a Realm", | ||||||
|   "createRealmHint": "Meet friends with same interests, build communities, and more.", |   "createRealmHint": "Meet friends with same interests, build communities, and more.", | ||||||
|   "editRealm": "Edit a Realm", |   "editRealm": "Edit Realm", | ||||||
|   "deleteRealm": "Delete 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.", |   "deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.", | ||||||
|   "explore": "Explore", |   "explore": "Explore", | ||||||
| @@ -72,9 +72,20 @@ | |||||||
|   "slug": "Slug", |   "slug": "Slug", | ||||||
|   "slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.", |   "slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.", | ||||||
|   "createChatRoom": "Create a Room", |   "createChatRoom": "Create a Room", | ||||||
|   "editChatRoom": "Edit a Room", |   "editChatRoom": "Edit Room", | ||||||
|  |   "deleteChatRoom": "Delete Room", | ||||||
|   "chat": "Chat", |   "chat": "Chat", | ||||||
|   "chatMessageHint": "Message in {}", |   "chatMessageHint": "Message in {}", | ||||||
|   "chatDirectMessageHint": "Message to {}", |   "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" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,30 +7,13 @@ import 'package:uuid/uuid.dart'; | |||||||
|  |  | ||||||
| class MessageRepository { | class MessageRepository { | ||||||
|   final SnChat room; |   final SnChat room; | ||||||
|  |   final SnChatMember identity; | ||||||
|   final Dio _apiClient; |   final Dio _apiClient; | ||||||
|   final AppDatabase _database; |   final AppDatabase _database; | ||||||
|  |  | ||||||
|   SnChatMember? _identity; |  | ||||||
|  |  | ||||||
|   final Map<String, LocalChatMessage> pendingMessages = {}; |   final Map<String, LocalChatMessage> pendingMessages = {}; | ||||||
|  |  | ||||||
|   MessageRepository(this.room, this._apiClient, this._database) { |   MessageRepository(this.room, this.identity, 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; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<List<LocalChatMessage>> listMessages({ |   Future<List<LocalChatMessage>> listMessages({ | ||||||
|     int offset = 0, |     int offset = 0, | ||||||
| @@ -143,12 +126,6 @@ class MessageRepository { | |||||||
|     List<SnCloudFile>? attachments, |     List<SnCloudFile>? attachments, | ||||||
|     Map<String, dynamic>? meta, |     Map<String, dynamic>? meta, | ||||||
|   }) async { |   }) async { | ||||||
|     if (!initialized || _identity == null) { |  | ||||||
|       throw UnsupportedError( |  | ||||||
|         "The message repository is not ready for send message.", |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Generate a unique nonce for this message |     // Generate a unique nonce for this message | ||||||
|     final nonce = const Uuid().v4(); |     final nonce = const Uuid().v4(); | ||||||
|  |  | ||||||
| @@ -156,12 +133,12 @@ class MessageRepository { | |||||||
|     final mockMessage = SnChatMessage( |     final mockMessage = SnChatMessage( | ||||||
|       id: 'pending_$nonce', |       id: 'pending_$nonce', | ||||||
|       chatRoomId: roomId, |       chatRoomId: roomId, | ||||||
|       senderId: _identity!.id, |       senderId: identity.id, | ||||||
|       content: content, |       content: content, | ||||||
|       createdAt: DateTime.now(), |       createdAt: DateTime.now(), | ||||||
|       updatedAt: DateTime.now(), |       updatedAt: DateTime.now(), | ||||||
|       nonce: nonce, |       nonce: nonce, | ||||||
|       sender: _identity!, |       sender: identity, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final localMessage = LocalChatMessage.fromRemoteMessage( |     final localMessage = LocalChatMessage.fromRemoteMessage( | ||||||
|   | |||||||
| @@ -81,6 +81,7 @@ abstract class SnChatMember with _$SnChatMember { | |||||||
|     required DateTime? deletedAt, |     required DateTime? deletedAt, | ||||||
|     required String id, |     required String id, | ||||||
|     required int chatRoomId, |     required int chatRoomId, | ||||||
|  |     required SnChat? chatRoom, | ||||||
|     required int accountId, |     required int accountId, | ||||||
|     required SnAccount account, |     required SnAccount account, | ||||||
|     required String? nick, |     required String? nick, | ||||||
|   | |||||||
| @@ -709,7 +709,7 @@ $SnChatMemberCopyWith<$Res> get sender { | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChatMember { | 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 | /// Create a copy of SnChatMember | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @@ -722,16 +722,16 @@ $SnChatMemberCopyWith<SnChatMember> get copyWith => _$SnChatMemberCopyWithImpl<S | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | 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) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @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 | @override | ||||||
| String toString() { | 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; |   factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $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 | /// @nodoc | ||||||
| @@ -759,14 +759,15 @@ class _$SnChatMemberCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatMember | /// Create a copy of SnChatMember | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// 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( |   return _then(_self.copyWith( | ||||||
| createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | 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,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,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 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 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 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 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 | 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. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @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 { | $SnAccountCopyWith<$Res> get account { | ||||||
|    |    | ||||||
|   return $SnAccountCopyWith<$Res>(_self.account, (value) { |   return $SnAccountCopyWith<$Res>(_self.account, (value) { | ||||||
| @@ -793,7 +806,7 @@ $SnAccountCopyWith<$Res> get account { | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnChatMember implements SnChatMember { | 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); |   factory _SnChatMember.fromJson(Map<String, dynamic> json) => _$SnChatMemberFromJson(json); | ||||||
|  |  | ||||||
| @override final  DateTime createdAt; | @override final  DateTime createdAt; | ||||||
| @@ -801,6 +814,7 @@ class _SnChatMember implements SnChatMember { | |||||||
| @override final  DateTime? deletedAt; | @override final  DateTime? deletedAt; | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @override final  int chatRoomId; | @override final  int chatRoomId; | ||||||
|  | @override final  SnChat? chatRoom; | ||||||
| @override final  int accountId; | @override final  int accountId; | ||||||
| @override final  SnAccount account; | @override final  SnAccount account; | ||||||
| @override final  String? nick; | @override final  String? nick; | ||||||
| @@ -822,16 +836,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | 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) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @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 | @override | ||||||
| String toString() { | 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; |   factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $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 | /// @nodoc | ||||||
| @@ -859,14 +873,15 @@ class __$SnChatMemberCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatMember | /// Create a copy of SnChatMember | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// 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( |   return _then(_SnChatMember( | ||||||
| createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | 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,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,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 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 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 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 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 | 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. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @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 { | $SnAccountCopyWith<$Res> get account { | ||||||
|    |    | ||||||
|   return $SnAccountCopyWith<$Res>(_self.account, (value) { |   return $SnAccountCopyWith<$Res>(_self.account, (value) { | ||||||
|   | |||||||
| @@ -163,6 +163,10 @@ _SnChatMember _$SnChatMemberFromJson(Map<String, dynamic> json) => | |||||||
|               : DateTime.parse(json['deleted_at'] as String), |               : DateTime.parse(json['deleted_at'] as String), | ||||||
|       id: json['id'] as String, |       id: json['id'] as String, | ||||||
|       chatRoomId: (json['chat_room_id'] as num).toInt(), |       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(), |       accountId: (json['account_id'] as num).toInt(), | ||||||
|       account: SnAccount.fromJson(json['account'] as Map<String, dynamic>), |       account: SnAccount.fromJson(json['account'] as Map<String, dynamic>), | ||||||
|       nick: json['nick'] as String?, |       nick: json['nick'] as String?, | ||||||
| @@ -182,6 +186,7 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) => | |||||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|       'id': instance.id, |       'id': instance.id, | ||||||
|       'chat_room_id': instance.chatRoomId, |       'chat_room_id': instance.chatRoomId, | ||||||
|  |       'chat_room': instance.chatRoom?.toJson(), | ||||||
|       'account_id': instance.accountId, |       'account_id': instance.accountId, | ||||||
|       'account': instance.account.toJson(), |       'account': instance.account.toJson(), | ||||||
|       'nick': instance.nick, |       'nick': instance.nick, | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import 'dart:developer'; | ||||||
|  |  | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/user.dart'; | ||||||
| @@ -21,6 +23,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|       final user = SnAccount.fromJson(response.data); |       final user = SnAccount.fromJson(response.data); | ||||||
|       state = AsyncValue.data(user); |       state = AsyncValue.data(user); | ||||||
|     } catch (error, stackTrace) { |     } catch (error, stackTrace) { | ||||||
|  |       log("[UserInfo] Failed to fetch user info: $error"); | ||||||
|       state = AsyncValue.error(error, stackTrace); |       state = AsyncValue.error(error, stackTrace); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -29,6 +32,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|     state = const AsyncValue.data(null); |     state = const AsyncValue.data(null); | ||||||
|     final prefs = _ref.read(sharedPreferencesProvider); |     final prefs = _ref.read(sharedPreferencesProvider); | ||||||
|     await prefs.remove(kTokenPairStoreKey); |     await prefs.remove(kTokenPairStoreKey); | ||||||
|  |     _ref.refresh(userInfoProvider.notifier); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  | import 'dart:convert'; | ||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
|  | import 'dart:typed_data'; | ||||||
|  |  | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.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'; | import 'package:web_socket_channel/web_socket_channel.dart'; | ||||||
|  |  | ||||||
| part 'websocket.freezed.dart'; | part 'websocket.freezed.dart'; | ||||||
|  | part 'websocket.g.dart'; | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| class WebSocketState with _$WebSocketState { | class WebSocketState with _$WebSocketState { | ||||||
| @@ -17,15 +21,35 @@ class WebSocketState with _$WebSocketState { | |||||||
|   const factory WebSocketState.error(String message) = _Error; |   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) { | final websocketProvider = Provider<WebSocketService>((ref) { | ||||||
|   return WebSocketService(); |   return WebSocketService(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| class WebSocketService { | class WebSocketService { | ||||||
|   WebSocketChannel? _channel; |   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 { |   Future<void> connect(String url, String atk) async { | ||||||
|  |     _lastUrl = url; | ||||||
|  |     _lastAtk = atk; | ||||||
|     log('[WebSocket] Trying connecting to $url'); |     log('[WebSocket] Trying connecting to $url'); | ||||||
|     try { |     try { | ||||||
|       _channel = IOWebSocketChannel.connect( |       _channel = IOWebSocketChannel.connect( | ||||||
| @@ -33,20 +57,48 @@ class WebSocketService { | |||||||
|         headers: {'Authorization': 'Bearer $atk'}, |         headers: {'Authorization': 'Bearer $atk'}, | ||||||
|       ); |       ); | ||||||
|       await _channel!.ready; |       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) { |     } catch (err) { | ||||||
|       log('[WebSocket] Failed to connect: $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; |   WebSocketChannel? get ws => _channel; | ||||||
|   Stream<dynamic> get stream => _broadcastStream!; |  | ||||||
|  |  | ||||||
|   void sendMessage(String message) { |   void sendMessage(String message) { | ||||||
|     _channel!.sink.add(message); |     _channel!.sink.add(message); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void close() { |   void close() { | ||||||
|  |     _reconnectTimer?.cancel(); | ||||||
|  |     _lastUrl = null; | ||||||
|  |     _lastAtk = null; | ||||||
|     _channel?.sink.close(); |     _channel?.sink.close(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | // dart format on | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								lib/pods/websocket.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								lib/pods/websocket.g.dart
									
									
									
									
									
										Normal 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, | ||||||
|  |     }; | ||||||
| @@ -37,5 +37,6 @@ class AppRouter extends RootStackRouter { | |||||||
|     AutoRoute(page: NewChatRoute.page, path: '/chat/new'), |     AutoRoute(page: NewChatRoute.page, path: '/chat/new'), | ||||||
|     AutoRoute(page: EditChatRoute.page, path: '/chat/:id/edit'), |     AutoRoute(page: EditChatRoute.page, path: '/chat/:id/edit'), | ||||||
|     AutoRoute(page: ChatRoomRoute.page, path: '/chat/:id'), |     AutoRoute(page: ChatRoomRoute.page, path: '/chat/:id'), | ||||||
|  |     AutoRoute(page: ChatDetailRoute.page, path: '/chat/:id/detail'), | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,32 +9,33 @@ | |||||||
| // coverage:ignore-file | // coverage:ignore-file | ||||||
|  |  | ||||||
| // ignore_for_file: no_leading_underscores_for_library_prefixes | // ignore_for_file: no_leading_underscores_for_library_prefixes | ||||||
| import 'package:auto_route/auto_route.dart' as _i14; | import 'package:auto_route/auto_route.dart' as _i15; | ||||||
| import 'package:flutter/material.dart' as _i15; | import 'package:flutter/material.dart' as _i16; | ||||||
| import 'package:island/models/post.dart' as _i16; | import 'package:island/models/post.dart' as _i17; | ||||||
| import 'package:island/screens/account.dart' as _i1; | import 'package:island/screens/account.dart' as _i1; | ||||||
| import 'package:island/screens/account/me.dart' as _i9; | import 'package:island/screens/account/me.dart' as _i10; | ||||||
| import 'package:island/screens/account/me/publishers.dart' as _i5; | import 'package:island/screens/account/me/publishers.dart' as _i6; | ||||||
| import 'package:island/screens/account/me/update.dart' as _i13; | import 'package:island/screens/account/me/update.dart' as _i14; | ||||||
| import 'package:island/screens/auth/create_account.dart' as _i4; | import 'package:island/screens/auth/create_account.dart' as _i5; | ||||||
| import 'package:island/screens/auth/login.dart' as _i8; | import 'package:island/screens/auth/login.dart' as _i9; | ||||||
| import 'package:island/screens/auth/tabs.dart' as _i12; | import 'package:island/screens/auth/tabs.dart' as _i13; | ||||||
| import 'package:island/screens/chat/chat.dart' as _i2; | import 'package:island/screens/chat/chat.dart' as _i3; | ||||||
| import 'package:island/screens/chat/room.dart' as _i3; | import 'package:island/screens/chat/room.dart' as _i4; | ||||||
| import 'package:island/screens/explore.dart' as _i7; | import 'package:island/screens/chat/room_detail.dart' as _i2; | ||||||
| import 'package:island/screens/posts/compose.dart' as _i10; | import 'package:island/screens/explore.dart' as _i8; | ||||||
| import 'package:island/screens/posts/detail.dart' as _i11; | import 'package:island/screens/posts/compose.dart' as _i11; | ||||||
| import 'package:island/screens/realm/realms.dart' as _i6; | import 'package:island/screens/posts/detail.dart' as _i12; | ||||||
|  | import 'package:island/screens/realm/realms.dart' as _i7; | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i1.AccountScreen] | /// [_i1.AccountScreen] | ||||||
| class AccountRoute extends _i14.PageRouteInfo<void> { | class AccountRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const AccountRoute({List<_i14.PageRouteInfo>? children}) |   const AccountRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(AccountRoute.name, initialChildren: children); |     : super(AccountRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'AccountRoute'; |   static const String name = 'AccountRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i1.AccountScreen(); |       return const _i1.AccountScreen(); | ||||||
| @@ -43,28 +44,69 @@ class AccountRoute extends _i14.PageRouteInfo<void> { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i2.ChatListScreen] | /// [_i2.ChatDetailScreen] | ||||||
| class ChatListRoute extends _i14.PageRouteInfo<void> { | class ChatDetailRoute extends _i15.PageRouteInfo<ChatDetailRouteArgs> { | ||||||
|   const ChatListRoute({List<_i14.PageRouteInfo>? children}) |   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); |     : super(ChatListRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'ChatListRoute'; |   static const String name = 'ChatListRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i2.ChatListScreen(); |       return const _i3.ChatListScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i3.ChatRoomScreen] | /// [_i4.ChatRoomScreen] | ||||||
| class ChatRoomRoute extends _i14.PageRouteInfo<ChatRoomRouteArgs> { | class ChatRoomRoute extends _i15.PageRouteInfo<ChatRoomRouteArgs> { | ||||||
|   ChatRoomRoute({ |   ChatRoomRoute({ | ||||||
|     _i15.Key? key, |     _i16.Key? key, | ||||||
|     required int id, |     required int id, | ||||||
|     List<_i14.PageRouteInfo>? children, |     List<_i15.PageRouteInfo>? children, | ||||||
|   }) : super( |   }) : super( | ||||||
|          ChatRoomRoute.name, |          ChatRoomRoute.name, | ||||||
|          args: ChatRoomRouteArgs(key: key, id: id), |          args: ChatRoomRouteArgs(key: key, id: id), | ||||||
| @@ -74,14 +116,14 @@ class ChatRoomRoute extends _i14.PageRouteInfo<ChatRoomRouteArgs> { | |||||||
|  |  | ||||||
|   static const String name = 'ChatRoomRoute'; |   static const String name = 'ChatRoomRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       final pathParams = data.inheritedPathParams; |       final pathParams = data.inheritedPathParams; | ||||||
|       final args = data.argsAs<ChatRoomRouteArgs>( |       final args = data.argsAs<ChatRoomRouteArgs>( | ||||||
|         orElse: () => ChatRoomRouteArgs(id: pathParams.getInt('id')), |         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 { | class ChatRoomRouteArgs { | ||||||
|   const ChatRoomRouteArgs({this.key, required this.id}); |   const ChatRoomRouteArgs({this.key, required this.id}); | ||||||
|  |  | ||||||
|   final _i15.Key? key; |   final _i16.Key? key; | ||||||
|  |  | ||||||
|   final int id; |   final int id; | ||||||
|  |  | ||||||
| @@ -100,25 +142,25 @@ class ChatRoomRouteArgs { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i4.CreateAccountScreen] | /// [_i5.CreateAccountScreen] | ||||||
| class CreateAccountRoute extends _i14.PageRouteInfo<void> { | class CreateAccountRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const CreateAccountRoute({List<_i14.PageRouteInfo>? children}) |   const CreateAccountRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(CreateAccountRoute.name, initialChildren: children); |     : super(CreateAccountRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'CreateAccountRoute'; |   static const String name = 'CreateAccountRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i4.CreateAccountScreen(); |       return const _i5.CreateAccountScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i2.EditChatScreen] | /// [_i3.EditChatScreen] | ||||||
| class EditChatRoute extends _i14.PageRouteInfo<EditChatRouteArgs> { | class EditChatRoute extends _i15.PageRouteInfo<EditChatRouteArgs> { | ||||||
|   EditChatRoute({_i15.Key? key, int? id, List<_i14.PageRouteInfo>? children}) |   EditChatRoute({_i16.Key? key, int? id, List<_i15.PageRouteInfo>? children}) | ||||||
|     : super( |     : super( | ||||||
|         EditChatRoute.name, |         EditChatRoute.name, | ||||||
|         args: EditChatRouteArgs(key: key, id: id), |         args: EditChatRouteArgs(key: key, id: id), | ||||||
| @@ -128,14 +170,14 @@ class EditChatRoute extends _i14.PageRouteInfo<EditChatRouteArgs> { | |||||||
|  |  | ||||||
|   static const String name = 'EditChatRoute'; |   static const String name = 'EditChatRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       final pathParams = data.inheritedPathParams; |       final pathParams = data.inheritedPathParams; | ||||||
|       final args = data.argsAs<EditChatRouteArgs>( |       final args = data.argsAs<EditChatRouteArgs>( | ||||||
|         orElse: () => EditChatRouteArgs(id: pathParams.optInt('id')), |         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 { | class EditChatRouteArgs { | ||||||
|   const EditChatRouteArgs({this.key, this.id}); |   const EditChatRouteArgs({this.key, this.id}); | ||||||
|  |  | ||||||
|   final _i15.Key? key; |   final _i16.Key? key; | ||||||
|  |  | ||||||
|   final int? id; |   final int? id; | ||||||
|  |  | ||||||
| @@ -154,12 +196,12 @@ class EditChatRouteArgs { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i5.EditPublisherScreen] | /// [_i6.EditPublisherScreen] | ||||||
| class EditPublisherRoute extends _i14.PageRouteInfo<EditPublisherRouteArgs> { | class EditPublisherRoute extends _i15.PageRouteInfo<EditPublisherRouteArgs> { | ||||||
|   EditPublisherRoute({ |   EditPublisherRoute({ | ||||||
|     _i15.Key? key, |     _i16.Key? key, | ||||||
|     String? name, |     String? name, | ||||||
|     List<_i14.PageRouteInfo>? children, |     List<_i15.PageRouteInfo>? children, | ||||||
|   }) : super( |   }) : super( | ||||||
|          EditPublisherRoute.name, |          EditPublisherRoute.name, | ||||||
|          args: EditPublisherRouteArgs(key: key, name: name), |          args: EditPublisherRouteArgs(key: key, name: name), | ||||||
| @@ -169,14 +211,14 @@ class EditPublisherRoute extends _i14.PageRouteInfo<EditPublisherRouteArgs> { | |||||||
|  |  | ||||||
|   static const String name = 'EditPublisherRoute'; |   static const String name = 'EditPublisherRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       final pathParams = data.inheritedPathParams; |       final pathParams = data.inheritedPathParams; | ||||||
|       final args = data.argsAs<EditPublisherRouteArgs>( |       final args = data.argsAs<EditPublisherRouteArgs>( | ||||||
|         orElse: () => EditPublisherRouteArgs(name: pathParams.optString('id')), |         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 { | class EditPublisherRouteArgs { | ||||||
|   const EditPublisherRouteArgs({this.key, this.name}); |   const EditPublisherRouteArgs({this.key, this.name}); | ||||||
|  |  | ||||||
|   final _i15.Key? key; |   final _i16.Key? key; | ||||||
|  |  | ||||||
|   final String? name; |   final String? name; | ||||||
|  |  | ||||||
| @@ -195,12 +237,12 @@ class EditPublisherRouteArgs { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i6.EditRealmScreen] | /// [_i7.EditRealmScreen] | ||||||
| class EditRealmRoute extends _i14.PageRouteInfo<EditRealmRouteArgs> { | class EditRealmRoute extends _i15.PageRouteInfo<EditRealmRouteArgs> { | ||||||
|   EditRealmRoute({ |   EditRealmRoute({ | ||||||
|     _i15.Key? key, |     _i16.Key? key, | ||||||
|     String? slug, |     String? slug, | ||||||
|     List<_i14.PageRouteInfo>? children, |     List<_i15.PageRouteInfo>? children, | ||||||
|   }) : super( |   }) : super( | ||||||
|          EditRealmRoute.name, |          EditRealmRoute.name, | ||||||
|          args: EditRealmRouteArgs(key: key, slug: slug), |          args: EditRealmRouteArgs(key: key, slug: slug), | ||||||
| @@ -210,14 +252,14 @@ class EditRealmRoute extends _i14.PageRouteInfo<EditRealmRouteArgs> { | |||||||
|  |  | ||||||
|   static const String name = 'EditRealmRoute'; |   static const String name = 'EditRealmRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       final pathParams = data.inheritedPathParams; |       final pathParams = data.inheritedPathParams; | ||||||
|       final args = data.argsAs<EditRealmRouteArgs>( |       final args = data.argsAs<EditRealmRouteArgs>( | ||||||
|         orElse: () => EditRealmRouteArgs(slug: pathParams.optString('slug')), |         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 { | class EditRealmRouteArgs { | ||||||
|   const EditRealmRouteArgs({this.key, this.slug}); |   const EditRealmRouteArgs({this.key, this.slug}); | ||||||
|  |  | ||||||
|   final _i15.Key? key; |   final _i16.Key? key; | ||||||
|  |  | ||||||
|   final String? slug; |   final String? slug; | ||||||
|  |  | ||||||
| @@ -236,124 +278,124 @@ class EditRealmRouteArgs { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i7.ExploreScreen] | /// [_i8.ExploreScreen] | ||||||
| class ExploreRoute extends _i14.PageRouteInfo<void> { | class ExploreRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const ExploreRoute({List<_i14.PageRouteInfo>? children}) |   const ExploreRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(ExploreRoute.name, initialChildren: children); |     : super(ExploreRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'ExploreRoute'; |   static const String name = 'ExploreRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i7.ExploreScreen(); |       return const _i8.ExploreScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i8.LoginScreen] | /// [_i9.LoginScreen] | ||||||
| class LoginRoute extends _i14.PageRouteInfo<void> { | class LoginRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const LoginRoute({List<_i14.PageRouteInfo>? children}) |   const LoginRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(LoginRoute.name, initialChildren: children); |     : super(LoginRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'LoginRoute'; |   static const String name = 'LoginRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i8.LoginScreen(); |       return const _i9.LoginScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i5.ManagedPublisherScreen] | /// [_i6.ManagedPublisherScreen] | ||||||
| class ManagedPublisherRoute extends _i14.PageRouteInfo<void> { | class ManagedPublisherRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const ManagedPublisherRoute({List<_i14.PageRouteInfo>? children}) |   const ManagedPublisherRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(ManagedPublisherRoute.name, initialChildren: children); |     : super(ManagedPublisherRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'ManagedPublisherRoute'; |   static const String name = 'ManagedPublisherRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i5.ManagedPublisherScreen(); |       return const _i6.ManagedPublisherScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i9.MyselfProfileScreen] | /// [_i10.MyselfProfileScreen] | ||||||
| class MyselfProfileRoute extends _i14.PageRouteInfo<void> { | class MyselfProfileRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const MyselfProfileRoute({List<_i14.PageRouteInfo>? children}) |   const MyselfProfileRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(MyselfProfileRoute.name, initialChildren: children); |     : super(MyselfProfileRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'MyselfProfileRoute'; |   static const String name = 'MyselfProfileRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i9.MyselfProfileScreen(); |       return const _i10.MyselfProfileScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i2.NewChatScreen] | /// [_i3.NewChatScreen] | ||||||
| class NewChatRoute extends _i14.PageRouteInfo<void> { | class NewChatRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const NewChatRoute({List<_i14.PageRouteInfo>? children}) |   const NewChatRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(NewChatRoute.name, initialChildren: children); |     : super(NewChatRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'NewChatRoute'; |   static const String name = 'NewChatRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i2.NewChatScreen(); |       return const _i3.NewChatScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i5.NewPublisherScreen] | /// [_i6.NewPublisherScreen] | ||||||
| class NewPublisherRoute extends _i14.PageRouteInfo<void> { | class NewPublisherRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const NewPublisherRoute({List<_i14.PageRouteInfo>? children}) |   const NewPublisherRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(NewPublisherRoute.name, initialChildren: children); |     : super(NewPublisherRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'NewPublisherRoute'; |   static const String name = 'NewPublisherRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i5.NewPublisherScreen(); |       return const _i6.NewPublisherScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i6.NewRealmScreen] | /// [_i7.NewRealmScreen] | ||||||
| class NewRealmRoute extends _i14.PageRouteInfo<void> { | class NewRealmRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const NewRealmRoute({List<_i14.PageRouteInfo>? children}) |   const NewRealmRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(NewRealmRoute.name, initialChildren: children); |     : super(NewRealmRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'NewRealmRoute'; |   static const String name = 'NewRealmRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i6.NewRealmScreen(); |       return const _i7.NewRealmScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i10.PostComposeScreen] | /// [_i11.PostComposeScreen] | ||||||
| class PostComposeRoute extends _i14.PageRouteInfo<PostComposeRouteArgs> { | class PostComposeRoute extends _i15.PageRouteInfo<PostComposeRouteArgs> { | ||||||
|   PostComposeRoute({ |   PostComposeRoute({ | ||||||
|     _i15.Key? key, |     _i16.Key? key, | ||||||
|     _i16.SnPost? originalPost, |     _i17.SnPost? originalPost, | ||||||
|     List<_i14.PageRouteInfo>? children, |     List<_i15.PageRouteInfo>? children, | ||||||
|   }) : super( |   }) : super( | ||||||
|          PostComposeRoute.name, |          PostComposeRoute.name, | ||||||
|          args: PostComposeRouteArgs(key: key, originalPost: originalPost), |          args: PostComposeRouteArgs(key: key, originalPost: originalPost), | ||||||
| @@ -362,13 +404,13 @@ class PostComposeRoute extends _i14.PageRouteInfo<PostComposeRouteArgs> { | |||||||
|  |  | ||||||
|   static const String name = 'PostComposeRoute'; |   static const String name = 'PostComposeRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       final args = data.argsAs<PostComposeRouteArgs>( |       final args = data.argsAs<PostComposeRouteArgs>( | ||||||
|         orElse: () => const PostComposeRouteArgs(), |         orElse: () => const PostComposeRouteArgs(), | ||||||
|       ); |       ); | ||||||
|       return _i10.PostComposeScreen( |       return _i11.PostComposeScreen( | ||||||
|         key: args.key, |         key: args.key, | ||||||
|         originalPost: args.originalPost, |         originalPost: args.originalPost, | ||||||
|       ); |       ); | ||||||
| @@ -379,9 +421,9 @@ class PostComposeRoute extends _i14.PageRouteInfo<PostComposeRouteArgs> { | |||||||
| class PostComposeRouteArgs { | class PostComposeRouteArgs { | ||||||
|   const PostComposeRouteArgs({this.key, this.originalPost}); |   const PostComposeRouteArgs({this.key, this.originalPost}); | ||||||
|  |  | ||||||
|   final _i15.Key? key; |   final _i16.Key? key; | ||||||
|  |  | ||||||
|   final _i16.SnPost? originalPost; |   final _i17.SnPost? originalPost; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
| @@ -390,12 +432,12 @@ class PostComposeRouteArgs { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i11.PostDetailScreen] | /// [_i12.PostDetailScreen] | ||||||
| class PostDetailRoute extends _i14.PageRouteInfo<PostDetailRouteArgs> { | class PostDetailRoute extends _i15.PageRouteInfo<PostDetailRouteArgs> { | ||||||
|   PostDetailRoute({ |   PostDetailRoute({ | ||||||
|     _i15.Key? key, |     _i16.Key? key, | ||||||
|     required int id, |     required int id, | ||||||
|     List<_i14.PageRouteInfo>? children, |     List<_i15.PageRouteInfo>? children, | ||||||
|   }) : super( |   }) : super( | ||||||
|          PostDetailRoute.name, |          PostDetailRoute.name, | ||||||
|          args: PostDetailRouteArgs(key: key, id: id), |          args: PostDetailRouteArgs(key: key, id: id), | ||||||
| @@ -405,14 +447,14 @@ class PostDetailRoute extends _i14.PageRouteInfo<PostDetailRouteArgs> { | |||||||
|  |  | ||||||
|   static const String name = 'PostDetailRoute'; |   static const String name = 'PostDetailRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       final pathParams = data.inheritedPathParams; |       final pathParams = data.inheritedPathParams; | ||||||
|       final args = data.argsAs<PostDetailRouteArgs>( |       final args = data.argsAs<PostDetailRouteArgs>( | ||||||
|         orElse: () => PostDetailRouteArgs(id: pathParams.getInt('id')), |         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 { | class PostDetailRouteArgs { | ||||||
|   const PostDetailRouteArgs({this.key, required this.id}); |   const PostDetailRouteArgs({this.key, required this.id}); | ||||||
|  |  | ||||||
|   final _i15.Key? key; |   final _i16.Key? key; | ||||||
|  |  | ||||||
|   final int id; |   final int id; | ||||||
|  |  | ||||||
| @@ -431,12 +473,12 @@ class PostDetailRouteArgs { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i10.PostEditScreen] | /// [_i11.PostEditScreen] | ||||||
| class PostEditRoute extends _i14.PageRouteInfo<PostEditRouteArgs> { | class PostEditRoute extends _i15.PageRouteInfo<PostEditRouteArgs> { | ||||||
|   PostEditRoute({ |   PostEditRoute({ | ||||||
|     _i15.Key? key, |     _i16.Key? key, | ||||||
|     required int id, |     required int id, | ||||||
|     List<_i14.PageRouteInfo>? children, |     List<_i15.PageRouteInfo>? children, | ||||||
|   }) : super( |   }) : super( | ||||||
|          PostEditRoute.name, |          PostEditRoute.name, | ||||||
|          args: PostEditRouteArgs(key: key, id: id), |          args: PostEditRouteArgs(key: key, id: id), | ||||||
| @@ -446,14 +488,14 @@ class PostEditRoute extends _i14.PageRouteInfo<PostEditRouteArgs> { | |||||||
|  |  | ||||||
|   static const String name = 'PostEditRoute'; |   static const String name = 'PostEditRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       final pathParams = data.inheritedPathParams; |       final pathParams = data.inheritedPathParams; | ||||||
|       final args = data.argsAs<PostEditRouteArgs>( |       final args = data.argsAs<PostEditRouteArgs>( | ||||||
|         orElse: () => PostEditRouteArgs(id: pathParams.getInt('id')), |         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 { | class PostEditRouteArgs { | ||||||
|   const PostEditRouteArgs({this.key, required this.id}); |   const PostEditRouteArgs({this.key, required this.id}); | ||||||
|  |  | ||||||
|   final _i15.Key? key; |   final _i16.Key? key; | ||||||
|  |  | ||||||
|   final int id; |   final int id; | ||||||
|  |  | ||||||
| @@ -472,49 +514,49 @@ class PostEditRouteArgs { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i6.RealmListScreen] | /// [_i7.RealmListScreen] | ||||||
| class RealmListRoute extends _i14.PageRouteInfo<void> { | class RealmListRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const RealmListRoute({List<_i14.PageRouteInfo>? children}) |   const RealmListRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(RealmListRoute.name, initialChildren: children); |     : super(RealmListRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'RealmListRoute'; |   static const String name = 'RealmListRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i6.RealmListScreen(); |       return const _i7.RealmListScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i12.TabsScreen] | /// [_i13.TabsScreen] | ||||||
| class TabsRoute extends _i14.PageRouteInfo<void> { | class TabsRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const TabsRoute({List<_i14.PageRouteInfo>? children}) |   const TabsRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(TabsRoute.name, initialChildren: children); |     : super(TabsRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'TabsRoute'; |   static const String name = 'TabsRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i12.TabsScreen(); |       return const _i13.TabsScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// generated route for | /// generated route for | ||||||
| /// [_i13.UpdateProfileScreen] | /// [_i14.UpdateProfileScreen] | ||||||
| class UpdateProfileRoute extends _i14.PageRouteInfo<void> { | class UpdateProfileRoute extends _i15.PageRouteInfo<void> { | ||||||
|   const UpdateProfileRoute({List<_i14.PageRouteInfo>? children}) |   const UpdateProfileRoute({List<_i15.PageRouteInfo>? children}) | ||||||
|     : super(UpdateProfileRoute.name, initialChildren: children); |     : super(UpdateProfileRoute.name, initialChildren: children); | ||||||
|  |  | ||||||
|   static const String name = 'UpdateProfileRoute'; |   static const String name = 'UpdateProfileRoute'; | ||||||
|  |  | ||||||
|   static _i14.PageInfo page = _i14.PageInfo( |   static _i15.PageInfo page = _i15.PageInfo( | ||||||
|     name, |     name, | ||||||
|     builder: (data) { |     builder: (data) { | ||||||
|       return const _i13.UpdateProfileScreen(); |       return const _i14.UpdateProfileScreen(); | ||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,7 +22,9 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|  |  | ||||||
|     if (!user.hasValue) return _UnauthorizedAccountScreen(); |     if (!user.hasValue || user.value == null) { | ||||||
|  |       return _UnauthorizedAccountScreen(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar(title: const Text('Account')), |       appBar: AppBar(title: const Text('Account')), | ||||||
| @@ -52,7 +54,7 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                     spacing: 16, |                     spacing: 16, | ||||||
|                     children: [ |                     children: [ | ||||||
|                       ProfilePictureWidget( |                       ProfilePictureWidget( | ||||||
|                         fileId: user.value!.profile.pictureId, |                         fileId: user.value?.profile.pictureId, | ||||||
|                         radius: 24, |                         radius: 24, | ||||||
|                       ), |                       ), | ||||||
|                       Column( |                       Column( | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'package:dio/dio.dart'; | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:image_picker/image_picker.dart'; | import 'package:image_picker/image_picker.dart'; | ||||||
| import 'package:island/models/chat.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/app_scaffold.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.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:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| @@ -36,9 +38,23 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|     final chats = ref.watch(chatroomsJoinedProvider); |     final chats = ref.watch(chatroomsJoinedProvider); | ||||||
|  |  | ||||||
|     return AppScaffold( |     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( |       floatingActionButton: FloatingActionButton( | ||||||
|         key: Key("chat-page-fab"), |         heroTag: Key("chat-page-fab"), | ||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           context.pushRoute(NewChatRoute()); |           context.pushRoute(NewChatRoute()); | ||||||
|         }, |         }, | ||||||
| @@ -58,7 +74,7 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|                   final item = items[index]; |                   final item = items[index]; | ||||||
|                   return ListTile( |                   return ListTile( | ||||||
|                     leading: |                     leading: | ||||||
|                         item.picture == null |                         item.pictureId == null | ||||||
|                             ? CircleAvatar( |                             ? CircleAvatar( | ||||||
|                               child: Text(item.name[0].toUpperCase()), |                               child: Text(item.name[0].toUpperCase()), | ||||||
|                             ) |                             ) | ||||||
| @@ -87,6 +103,14 @@ Future<SnChat?> chatroom(Ref ref, int? identifier) async { | |||||||
|   return SnChat.fromJson(resp.data); |   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() | @RoutePage() | ||||||
| class NewChatScreen extends StatelessWidget { | class NewChatScreen extends StatelessWidget { | ||||||
|   const NewChatScreen({super.key}); |   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')), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -163,5 +163,146 @@ class _ChatroomProviderElement extends AutoDisposeFutureProviderElement<SnChat?> | |||||||
|   int? get identifier => (origin as ChatroomProvider).identifier; |   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: 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 | // 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 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import 'package:auto_route/annotations.dart'; | import 'package:auto_route/auto_route.dart'; | ||||||
| import 'package:collection/collection.dart'; | import 'package:collection/collection.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.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/database/message_repository.dart'; | ||||||
| import 'package:island/pods/message.dart'; | import 'package:island/pods/message.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/route.gr.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:material_symbols_icons/material_symbols_icons.dart'; | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
| @@ -19,9 +20,10 @@ import 'chat.dart'; | |||||||
| final messageRepositoryProvider = FutureProvider.family<MessageRepository, int>( | final messageRepositoryProvider = FutureProvider.family<MessageRepository, int>( | ||||||
|   (ref, roomId) async { |   (ref, roomId) async { | ||||||
|     final room = await ref.watch(chatroomProvider(roomId).future); |     final room = await ref.watch(chatroomProvider(roomId).future); | ||||||
|  |     final identity = await ref.watch(chatroomIdentityProvider(roomId).future); | ||||||
|     final apiClient = ref.watch(apiClientProvider); |     final apiClient = ref.watch(apiClientProvider); | ||||||
|     final database = ref.watch(databaseProvider); |     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 |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final chatRoom = ref.watch(chatroomProvider(id)); |     final chatRoom = ref.watch(chatroomProvider(id)); | ||||||
|  |     final chatIdentity = ref.watch(chatroomIdentityProvider(id)); | ||||||
|     final messages = ref.watch(messagesProvider(id)); |     final messages = ref.watch(messagesProvider(id)); | ||||||
|     final messagesNotifier = ref.read(messagesProvider(id).notifier); |     final messagesNotifier = ref.read(messagesProvider(id).notifier); | ||||||
|  |     final messagesRepo = ref.watch(messageRepositoryProvider(id)); | ||||||
|  |  | ||||||
|     final messageController = useTextEditingController(); |     final messageController = useTextEditingController(); | ||||||
|     final scrollController = useScrollController(); |     final scrollController = useScrollController(); | ||||||
| @@ -185,7 +189,7 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|                     height: 26, |                     height: 26, | ||||||
|                     width: 26, |                     width: 26, | ||||||
|                     child: |                     child: | ||||||
|                         room?.picture != null |                         room?.pictureId != null | ||||||
|                             ? ProfilePictureWidget( |                             ? ProfilePictureWidget( | ||||||
|                               fileId: room?.pictureId, |                               fileId: room?.pictureId, | ||||||
|                               fallbackIcon: Symbols.chat, |                               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...'), |           loading: () => const Text('Loading...'), | ||||||
|           error: (_, __) => const Text('Error'), |           error: (_, __) => const Text('Error'), | ||||||
|         ), |         ), | ||||||
|         actions: [ |         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), |           const Gap(8), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
| @@ -217,12 +226,27 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|                       messageList.isEmpty |                       messageList.isEmpty | ||||||
|                           ? Center(child: Text('No messages yet'.tr())) |                           ? Center(child: Text('No messages yet'.tr())) | ||||||
|                           : ListView.builder( |                           : ListView.builder( | ||||||
|  |                             padding: EdgeInsets.symmetric(vertical: 16), | ||||||
|                             controller: scrollController, |                             controller: scrollController, | ||||||
|                             reverse: true, // Show newest messages at the bottom |                             reverse: true, // Show newest messages at the bottom | ||||||
|                             itemCount: messageList.length, |                             itemCount: messageList.length, | ||||||
|                             itemBuilder: (context, index) { |                             itemBuilder: (context, index) { | ||||||
|                               final message = messageList[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()), |               loading: () => const Center(child: CircularProgressIndicator()), | ||||||
| @@ -302,14 +326,16 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
| class MessageBubble extends StatelessWidget { | class MessageBubble extends StatelessWidget { | ||||||
|   final LocalChatMessage message; |   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 |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final isCurrentUser = |  | ||||||
|         message.senderId == 'current_user_id'; // Replace with actual check |  | ||||||
|  |  | ||||||
|     return Padding( |     return Padding( | ||||||
|       padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), |       padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), | ||||||
|       child: Row( |       child: Row( | ||||||
| @@ -365,7 +391,11 @@ class MessageBubble extends StatelessWidget { | |||||||
|           ), |           ), | ||||||
|           const Gap(8), |           const Gap(8), | ||||||
|           if (isCurrentUser) |           if (isCurrentUser) | ||||||
|             const SizedBox(width: 32), // Balance with avatar on the other side |             ProfilePictureWidget( | ||||||
|  |               fileId: | ||||||
|  |                   message.toRemoteMessage().sender.account.profile.pictureId, | ||||||
|  |               radius: 16, | ||||||
|  |             ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
							
								
								
									
										382
									
								
								lib/screens/chat/room_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								lib/screens/chat/room_detail.dart
									
									
									
									
									
										Normal 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}"), | ||||||
|  |                                 ), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                           ); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										157
									
								
								lib/screens/chat/room_detail.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								lib/screens/chat/room_detail.freezed.dart
									
									
									
									
									
										Normal 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 | ||||||
| @@ -21,7 +21,7 @@ class ExploreScreen extends ConsumerWidget { | |||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar(title: const Text('Explore')), |       appBar: AppBar(title: const Text('Explore')), | ||||||
|       floatingActionButton: FloatingActionButton( |       floatingActionButton: FloatingActionButton( | ||||||
|         key: Key("explore-page-fab"), |         heroTag: Key("explore-page-fab"), | ||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           context.router.push(PostComposeRoute()).then((value) { |           context.router.push(PostComposeRoute()).then((value) { | ||||||
|             if (value != null) { |             if (value != null) { | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar(title: const Text('realms').tr()), |       appBar: AppBar(title: const Text('realms').tr()), | ||||||
|       floatingActionButton: FloatingActionButton( |       floatingActionButton: FloatingActionButton( | ||||||
|         key: Key("realms-page-fab"), |         heroTag: Key("realms-page-fab"), | ||||||
|         child: const Icon(Symbols.add), |         child: const Icon(Symbols.add), | ||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           context.router.push(NewRealmRoute()); |           context.router.push(NewRealmRoute()); | ||||||
|   | |||||||
							
								
								
									
										107
									
								
								lib/widgets/account/account_picker.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								lib/widgets/account/account_picker.dart
									
									
									
									
									
										Normal 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')), | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										153
									
								
								lib/widgets/account/account_picker.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								lib/widgets/account/account_picker.g.dart
									
									
									
									
									
										Normal 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 | ||||||
| @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/route.dart'; | import 'package:island/route.dart'; | ||||||
|  | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
| import 'package:path_provider/path_provider.dart'; | import 'package:path_provider/path_provider.dart'; | ||||||
| import 'package:responsive_framework/responsive_framework.dart'; | import 'package:responsive_framework/responsive_framework.dart'; | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| @@ -157,14 +158,21 @@ class AppScaffold extends StatelessWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class PageBackButton extends StatelessWidget { | class PageBackButton extends StatelessWidget { | ||||||
|   const PageBackButton({super.key}); |   final List<Shadow>? shadows; | ||||||
|  |   const PageBackButton({super.key, this.shadows}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return BackButton( |     return IconButton( | ||||||
|       onPressed: () { |       onPressed: () { | ||||||
|         context.router.maybePop(); |         context.router.maybePop(); | ||||||
|       }, |       }, | ||||||
|  |       icon: Icon( | ||||||
|  |         (!kIsWeb && (Platform.isMacOS || Platform.isIOS)) | ||||||
|  |             ? Symbols.arrow_back_ios_new | ||||||
|  |             : Symbols.arrow_back, | ||||||
|  |         shadows: shadows, | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 { | class ProfilePictureWidget extends ConsumerWidget { | ||||||
|   final String? fileId; |   final String? fileId; | ||||||
|   final double radius; |   final double radius; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| platform :osx, '10.14' | platform :osx, '10.15' | ||||||
|  |  | ||||||
| # CocoaPods analytics sends network stats synchronously affecting flutter build latency. | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. | ||||||
| ENV['COCOAPODS_DISABLE_STATS'] = 'true' | ENV['COCOAPODS_DISABLE_STATS'] = 'true' | ||||||
|   | |||||||
| @@ -1,29 +1,264 @@ | |||||||
| PODS: | PODS: | ||||||
|   - bitsdojo_window_macos (0.0.1): |   - bitsdojo_window_macos (0.0.1): | ||||||
|     - FlutterMacOS |     - 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) |   - 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): |   - shared_preferences_foundation (0.0.1): | ||||||
|     - Flutter |     - Flutter | ||||||
|     - FlutterMacOS |     - 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: | DEPENDENCIES: | ||||||
|   - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) |   - 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`) |   - 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`) |   - 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: | EXTERNAL SOURCES: | ||||||
|   bitsdojo_window_macos: |   bitsdojo_window_macos: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/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: |   FlutterMacOS: | ||||||
|     :path: Flutter/ephemeral |     :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: |   shared_preferences_foundation: | ||||||
|     :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin |     :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: | SPEC CHECKSUMS: | ||||||
|   bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9 |   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 |   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 |   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 | COCOAPODS: 1.16.2 | ||||||
|   | |||||||
| @@ -122,7 +122,6 @@ | |||||||
| 				97CE52E8413D7559BF9A7981 /* Pods-RunnerTests.release.xcconfig */, | 				97CE52E8413D7559BF9A7981 /* Pods-RunnerTests.release.xcconfig */, | ||||||
| 				30FD87E3D579B3530B39AD6D /* Pods-RunnerTests.profile.xcconfig */, | 				30FD87E3D579B3530B39AD6D /* Pods-RunnerTests.profile.xcconfig */, | ||||||
| 			); | 			); | ||||||
| 			name = Pods; |  | ||||||
| 			path = Pods; | 			path = Pods; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| 		}; | 		}; | ||||||
| @@ -244,6 +243,7 @@ | |||||||
| 				33CC110E2044A8840003C045 /* Bundle Framework */, | 				33CC110E2044A8840003C045 /* Bundle Framework */, | ||||||
| 				3399D490228B24CF009A79C7 /* ShellScript */, | 				3399D490228B24CF009A79C7 /* ShellScript */, | ||||||
| 				F1E275A871246799FC3019F6 /* [CP] Embed Pods Frameworks */, | 				F1E275A871246799FC3019F6 /* [CP] Embed Pods Frameworks */, | ||||||
|  | 				8D06F41203F1FD2FDE04DC7F /* [CP] Copy Pods Resources */, | ||||||
| 			); | 			); | ||||||
| 			buildRules = ( | 			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"; | 			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; | 			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 */ = { | 		B2D8214A877A92B7299E734E /* [CP] Check Pods Manifest.lock */ = { | ||||||
| 			isa = PBXShellScriptBuildPhase; | 			isa = PBXShellScriptBuildPhase; | ||||||
| 			buildActionMask = 2147483647; | 			buildActionMask = 2147483647; | ||||||
| @@ -583,6 +600,7 @@ | |||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/../Frameworks", | 					"@executable_path/../Frameworks", | ||||||
| 				); | 				); | ||||||
|  | 				MACOSX_DEPLOYMENT_TARGET = 10.15; | ||||||
| 				PROVISIONING_PROFILE_SPECIFIER = ""; | 				PROVISIONING_PROFILE_SPECIFIER = ""; | ||||||
| 				SWIFT_VERSION = 5.0; | 				SWIFT_VERSION = 5.0; | ||||||
| 			}; | 			}; | ||||||
| @@ -715,6 +733,7 @@ | |||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/../Frameworks", | 					"@executable_path/../Frameworks", | ||||||
| 				); | 				); | ||||||
|  | 				MACOSX_DEPLOYMENT_TARGET = 10.15; | ||||||
| 				PROVISIONING_PROFILE_SPECIFIER = ""; | 				PROVISIONING_PROFILE_SPECIFIER = ""; | ||||||
| 				SWIFT_OPTIMIZATION_LEVEL = "-Onone"; | 				SWIFT_OPTIMIZATION_LEVEL = "-Onone"; | ||||||
| 				SWIFT_VERSION = 5.0; | 				SWIFT_VERSION = 5.0; | ||||||
| @@ -735,6 +754,7 @@ | |||||||
| 					"$(inherited)", | 					"$(inherited)", | ||||||
| 					"@executable_path/../Frameworks", | 					"@executable_path/../Frameworks", | ||||||
| 				); | 				); | ||||||
|  | 				MACOSX_DEPLOYMENT_TARGET = 10.15; | ||||||
| 				PROVISIONING_PROFILE_SPECIFIER = ""; | 				PROVISIONING_PROFILE_SPECIFIER = ""; | ||||||
| 				SWIFT_VERSION = 5.0; | 				SWIFT_VERSION = 5.0; | ||||||
| 			}; | 			}; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user