Compare commits
	
		
			4 Commits
		
	
	
		
			047cb9dc0d
			...
			5939a1dc5b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5939a1dc5b | |||
| 9d115a5712 | |||
| f511612a53 | |||
| 180fbcc558 | 
| @@ -89,31 +89,14 @@ | |||||||
|   "authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.", |   "authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.", | ||||||
|   "authFactorPin": "Pin Code", |   "authFactorPin": "Pin Code", | ||||||
|   "authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.", |   "authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.", | ||||||
|   "realms": "Realms", |  | ||||||
|   "createRealm": "Create a Realm", |  | ||||||
|   "createRealmHint": "Meet friends with same interests, build communities, and more.", |  | ||||||
|   "editRealm": "Edit Realm", |  | ||||||
|   "deleteRealm": "Delete Realm", |  | ||||||
|   "deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.", |  | ||||||
|   "explore": "Explore", |   "explore": "Explore", | ||||||
|   "exploreFilterSubscriptions": "Subscriptions", |   "exploreFilterSubscriptions": "Subscriptions", | ||||||
|   "exploreFilterFriends": "Friends", |   "exploreFilterFriends": "Friends", | ||||||
|   "discoverCommunities": "Discover Communities", |   "discover": "Discover", | ||||||
|   "account": "Account", |   "account": "Account", | ||||||
|   "name": "Name", |   "name": "Name", | ||||||
|   "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", |  | ||||||
|   "editChatRoom": "Edit Room", |  | ||||||
|   "deleteChatRoom": "Delete Room", |  | ||||||
|   "deleteChatRoomHint": "Are you sure to delete this room? This action cannot be undone.", |  | ||||||
|   "chat": "Chat", |  | ||||||
|   "chatTabAll": "All", |  | ||||||
|   "chatTabDirect": "Direct Messages", |  | ||||||
|   "chatTabGroup": "Group Chats", |  | ||||||
|   "chatMessageHint": "Message in {}", |  | ||||||
|   "chatDirectMessageHint": "Message to {}", |  | ||||||
|   "directMessage": "Direct Message", |  | ||||||
|   "loading": "Loading...", |   "loading": "Loading...", | ||||||
|   "descriptionNone": "No description yet.", |   "descriptionNone": "No description yet.", | ||||||
|   "invites": "Invites", |   "invites": "Invites", | ||||||
| @@ -248,7 +231,6 @@ | |||||||
|   "uploadingProgress": "Uploading {} of {}", |   "uploadingProgress": "Uploading {} of {}", | ||||||
|   "uploadAll": "Upload All", |   "uploadAll": "Upload All", | ||||||
|   "stickerCopyPlaceholder": "Copy Placeholder", |   "stickerCopyPlaceholder": "Copy Placeholder", | ||||||
|   "realmSelection": "Select a Realm", |  | ||||||
|   "individual": "Individual", |   "individual": "Individual", | ||||||
|   "firstPostBadgeName": "First Post", |   "firstPostBadgeName": "First Post", | ||||||
|   "firstPostBadgeDescription": "Created your first post on Solar Network", |   "firstPostBadgeDescription": "Created your first post on Solar Network", | ||||||
| @@ -304,10 +286,6 @@ | |||||||
|   "levelingProgressExperience": "{} EXP", |   "levelingProgressExperience": "{} EXP", | ||||||
|   "levelingProgressLevel": "Level {}", |   "levelingProgressLevel": "Level {}", | ||||||
|   "fileUploadingProgress": "Uploading file #{}: {}%", |   "fileUploadingProgress": "Uploading file #{}: {}%", | ||||||
|   "removeChatMember": "Remove Chat Room Member", |  | ||||||
|   "removeChatMemberHint": "Are you sure to remove this member from the room?", |  | ||||||
|   "removeRealmMember": "Remove Realm Member", |  | ||||||
|   "removeRealmMemberHint": "Are you sure to remove this member from the realm?", |  | ||||||
|   "memberRole": "Member Role", |   "memberRole": "Member Role", | ||||||
|   "memberRoleHint": "Greater number has higher permission.", |   "memberRoleHint": "Greater number has higher permission.", | ||||||
|   "memberRoleEdit": "Edit role for @{}", |   "memberRoleEdit": "Edit role for @{}", | ||||||
| @@ -315,10 +293,6 @@ | |||||||
|   "openLinkConfirmDescription": "You're going to leave the Solar Network and open the link ({}) in your browser. It is not related to Solar Network. Beware of phishing and scams.", |   "openLinkConfirmDescription": "You're going to leave the Solar Network and open the link ({}) in your browser. It is not related to Solar Network. Beware of phishing and scams.", | ||||||
|   "brokenLink": "Unable open link {}... It might be broken or missing uri parts...", |   "brokenLink": "Unable open link {}... It might be broken or missing uri parts...", | ||||||
|   "copyToClipboard": "Copy to clipboard", |   "copyToClipboard": "Copy to clipboard", | ||||||
|   "leaveChatRoom": "Leave Chat Room", |  | ||||||
|   "leaveChatRoomHint": "Are you sure to leave this chat room?", |  | ||||||
|   "leaveRealm": "Leave Realm", |  | ||||||
|   "leaveRealmHint": "Are you sure to leave this realm?", |  | ||||||
|   "walletNotFound": "Wallet not found", |   "walletNotFound": "Wallet not found", | ||||||
|   "walletCreateHint": "You don't have a wallet yet. Create one to start using the Solar Network eWallet.", |   "walletCreateHint": "You don't have a wallet yet. Create one to start using the Solar Network eWallet.", | ||||||
|   "walletCreate": "Create a Wallet", |   "walletCreate": "Create a Wallet", | ||||||
| @@ -330,12 +304,6 @@ | |||||||
|   "settingsBackgroundImageClear": "Clear Background Image", |   "settingsBackgroundImageClear": "Clear Background Image", | ||||||
|   "settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image", |   "settingsBackgroundGenerateColor": "Generate color scheme from Bacground Image", | ||||||
|   "messageNone": "No content to display", |   "messageNone": "No content to display", | ||||||
|   "unreadMessages": { |  | ||||||
|     "one": "{} unread message", |  | ||||||
|     "other": "{} unread messages" |  | ||||||
|   }, |  | ||||||
|   "chatBreakNone": "None", |  | ||||||
|   "settingsRealmCompactView": "Compact Realm View", |  | ||||||
|   "settingsMixedFeed": "Mixed Feed", |   "settingsMixedFeed": "Mixed Feed", | ||||||
|   "settingsAutoTranslate": "Auto Translate", |   "settingsAutoTranslate": "Auto Translate", | ||||||
|   "settingsHideBottomNav": "Hide Bottom Navigation", |   "settingsHideBottomNav": "Hide Bottom Navigation", | ||||||
| @@ -378,7 +346,6 @@ | |||||||
|   "postVisibilityUnlisted": "Unlisted", |   "postVisibilityUnlisted": "Unlisted", | ||||||
|   "postVisibilityPrivate": "Private", |   "postVisibilityPrivate": "Private", | ||||||
|   "postTruncated": "Content truncated, tap to view full post", |   "postTruncated": "Content truncated, tap to view full post", | ||||||
|   "copyMessage": "Copy Message", |  | ||||||
|   "authFactor": "Authentication Factor", |   "authFactor": "Authentication Factor", | ||||||
|   "authFactorDelete": "Delete the Factor", |   "authFactorDelete": "Delete the Factor", | ||||||
|   "authFactorDeleteHint": "Are you sure you want to delete this authentication factor? This action cannot be undone.", |   "authFactorDeleteHint": "Are you sure you want to delete this authentication factor? This action cannot be undone.", | ||||||
| @@ -411,10 +378,6 @@ | |||||||
|   "authDeviceLabelHint": "Enter a name for this device", |   "authDeviceLabelHint": "Enter a name for this device", | ||||||
|   "authDeviceSwipeEditHint": "Swipe left to edit label", |   "authDeviceSwipeEditHint": "Swipe left to edit label", | ||||||
|   "authDeviceSwipeLogoutHint": "Swipe right to logout device", |   "authDeviceSwipeLogoutHint": "Swipe right to logout device", | ||||||
|   "typingHint": { |  | ||||||
|     "one": "{} is typing...", |  | ||||||
|     "other": "{} are typing..." |  | ||||||
|   }, |  | ||||||
|   "settingsAppearance": "Appearance", |   "settingsAppearance": "Appearance", | ||||||
|   "settingsServer": "Server", |   "settingsServer": "Server", | ||||||
|   "settingsBehavior": "Behavior", |   "settingsBehavior": "Behavior", | ||||||
| @@ -476,21 +439,6 @@ | |||||||
|   "contactMethodSetPrimary": "Set as Primary", |   "contactMethodSetPrimary": "Set as Primary", | ||||||
|   "contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications", |   "contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications", | ||||||
|   "contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.", |   "contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.", | ||||||
|   "chatNotifyLevel": "Notify Level", |  | ||||||
|   "chatNotifyLevelDescription": "Decide how many notifications you will receive.", |  | ||||||
|   "chatNotifyLevelAll": "All", |  | ||||||
|   "chatNotifyLevelMention": "Mentions", |  | ||||||
|   "chatNotifyLevelNone": "None", |  | ||||||
|   "chatNotifyLevelUpdated": "The notify level has been updated to {}.", |  | ||||||
|   "chatBreak": "Take a Break", |  | ||||||
|   "chatBreakDescription": "Set a time, before that time, your notification level will be metions only, to take a break of the current topic they're talking about.", |  | ||||||
|   "chatBreakClear": "Clear the break time", |  | ||||||
|   "chatBreakHour": "{} break", |  | ||||||
|   "chatBreakDay": "{} day break", |  | ||||||
|   "chatBreakSet": "Break set for {}", |  | ||||||
|   "chatBreakCleared": "Chat break has been cleared.", |  | ||||||
|   "chatBreakCustom": "Custom duration", |  | ||||||
|   "chatBreakEnterMinutes": "Enter minutes", |  | ||||||
|   "firstName": "First Name", |   "firstName": "First Name", | ||||||
|   "middleName": "Middle Name", |   "middleName": "Middle Name", | ||||||
|   "lastName": "Last Name", |   "lastName": "Last Name", | ||||||
| @@ -572,29 +520,17 @@ | |||||||
|   "quickActions": "Quick Actions", |   "quickActions": "Quick Actions", | ||||||
|   "post": "Post", |   "post": "Post", | ||||||
|   "copy": "Copy", |   "copy": "Copy", | ||||||
|   "sendToChat": "Send to Chat", |  | ||||||
|   "failedToShareToPost": "Failed to share to post: {}", |   "failedToShareToPost": "Failed to share to post: {}", | ||||||
|   "shareToChatComingSoon": "Share to chat functionality coming soon", |   "shareToChatComingSoon": "Share to chat functionality coming soon", | ||||||
|   "failedToShareToChat": "Failed to share to chat: {}", |  | ||||||
|   "shareToSpecificChatComingSoon": "Share to {} coming soon", |  | ||||||
|   "directChat": "Direct Chat", |  | ||||||
|   "systemShareComingSoon": "System share functionality coming soon", |   "systemShareComingSoon": "System share functionality coming soon", | ||||||
|   "failedToShareToSystem": "Failed to share to system: {}", |   "failedToShareToSystem": "Failed to share to system: {}", | ||||||
|   "failedToCopy": "Failed to copy: {}", |   "failedToCopy": "Failed to copy: {}", | ||||||
|   "noChatRoomsAvailable": "No chat rooms available", |  | ||||||
|   "failedToLoadChats": "Failed to load chats", |  | ||||||
|   "contentToShare": "Content to share:", |   "contentToShare": "Content to share:", | ||||||
|   "unknownChat": "Unknown Chat", |  | ||||||
|   "addAdditionalMessage": "Add additional message...", |  | ||||||
|   "uploadingFiles": "Uploading files...", |   "uploadingFiles": "Uploading files...", | ||||||
|   "sharedSuccessfully": "Shared successfully!", |  | ||||||
|   "shareSuccess": "Shared successfully!", |   "shareSuccess": "Shared successfully!", | ||||||
|   "shareToSpecificChatSuccess": "Shared to {} successfully!", |  | ||||||
|   "wouldYouLikeToGoToChat": "Would you like to go to the chat?", |   "wouldYouLikeToGoToChat": "Would you like to go to the chat?", | ||||||
|   "no": "No", |   "no": "No", | ||||||
|   "yes": "Yes", |   "yes": "Yes", | ||||||
|   "navigateToChat": "Navigate to Chat", |  | ||||||
|   "wouldYouLikeToNavigateToChat": "Would you like to navigate to the chat?", |  | ||||||
|   "abuseReport": "Report", |   "abuseReport": "Report", | ||||||
|   "abuseReportTitle": "Report Content", |   "abuseReportTitle": "Report Content", | ||||||
|   "abuseReportDescription": "Help us keep the community safe by reporting inappropriate content or behavior.", |   "abuseReportDescription": "Help us keep the community safe by reporting inappropriate content or behavior.", | ||||||
|   | |||||||
| @@ -29,6 +29,12 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface. | |||||||
| import 'package:flutter_native_splash/flutter_native_splash.dart'; | import 'package:flutter_native_splash/flutter_native_splash.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
|  | @pragma('vm:entry-point') | ||||||
|  | Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { | ||||||
|  |   await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); | ||||||
|  |   log('Handling a background message: ${message.messageId}'); | ||||||
|  | } | ||||||
|  |  | ||||||
| void main() async { | void main() async { | ||||||
|   final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); |   final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); | ||||||
|   if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { |   if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { | ||||||
| @@ -43,6 +49,7 @@ void main() async { | |||||||
|     await Firebase.initializeApp( |     await Firebase.initializeApp( | ||||||
|       options: DefaultFirebaseOptions.currentPlatform, |       options: DefaultFirebaseOptions.currentPlatform, | ||||||
|     ); |     ); | ||||||
|  |     FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); | ||||||
|     log("[SplashScreen] Firebase is ready!"); |     log("[SplashScreen] Firebase is ready!"); | ||||||
|   } catch (err) { |   } catch (err) { | ||||||
|     showErrorAlert(err); |     showErrorAlert(err); | ||||||
| @@ -151,17 +158,30 @@ class IslandApp extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|       Future(() async { |       // When the app is opened from a terminated state. | ||||||
|         RemoteMessage? initialMessage = |       FirebaseMessaging.instance.getInitialMessage().then((message) { | ||||||
|             await FirebaseMessaging.instance.getInitialMessage(); |         if (message != null) { | ||||||
|         if (initialMessage != null) { |           handleMessage(message); | ||||||
|           handleMessage(initialMessage); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); |  | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       return null; |       // When the app is in the background and opened. | ||||||
|  |       final onMessageOpenedAppSubscription = FirebaseMessaging | ||||||
|  |           .onMessageOpenedApp | ||||||
|  |           .listen(handleMessage); | ||||||
|  |  | ||||||
|  |       // When the app is in the foreground. | ||||||
|  |       final onMessageSubscription = FirebaseMessaging.onMessage.listen(( | ||||||
|  |         message, | ||||||
|  |       ) { | ||||||
|  |         log('Foreground message received: ${message.messageId}'); | ||||||
|  |         handleMessage(message); | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       return () { | ||||||
|  |         onMessageOpenedAppSubscription.cancel(); | ||||||
|  |         onMessageSubscription.cancel(); | ||||||
|  |       }; | ||||||
|     }, []); |     }, []); | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
| @@ -204,9 +224,8 @@ class IslandApp extends HookConsumerWidget { | |||||||
|           initialEntries: [ |           initialEntries: [ | ||||||
|             OverlayEntry( |             OverlayEntry( | ||||||
|               builder: |               builder: | ||||||
|                   (_) => WindowScaffold( |                   (_) => | ||||||
|                     child: child ?? const SizedBox.shrink(), |                       WindowScaffold(child: child ?? const SizedBox.shrink()), | ||||||
|                   ), |  | ||||||
|             ), |             ), | ||||||
|           ], |           ], | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ sealed class SnChatRoom with _$SnChatRoom { | |||||||
|     required String? description, |     required String? description, | ||||||
|     required int type, |     required int type, | ||||||
|     required bool isPublic, |     required bool isPublic, | ||||||
|  |     required bool isCommunity, | ||||||
|     required SnCloudFile? picture, |     required SnCloudFile? picture, | ||||||
|     required SnCloudFile? background, |     required SnCloudFile? background, | ||||||
|     required String? realmId, |     required String? realmId, | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChatRoom { | mixin _$SnChatRoom { | ||||||
|  |  | ||||||
|  String get id; String? get name; String? get description; int get type; bool get isPublic; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members; |  String get id; String? get name; String? get description; int get type; bool get isPublic; bool get isCommunity; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members; | ||||||
| /// Create a copy of SnChatRoom | /// Create a copy of SnChatRoom | ||||||
| /// 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) | ||||||
| @@ -29,16 +29,16 @@ $SnChatRoomCopyWith<SnChatRoom> get copyWith => _$SnChatRoomCopyWithImpl<SnChatR | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members)); | int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members)); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; |   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -49,7 +49,7 @@ abstract mixin class $SnChatRoomCopyWith<$Res>  { | |||||||
|   factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl; |   factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members |  String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -66,13 +66,14 @@ class _$SnChatRoomCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatRoom | /// Create a copy of SnChatRoom | ||||||
| /// 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? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||||
| as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -128,7 +129,7 @@ $SnRealmCopyWith<$Res>? get realm { | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnChatRoom implements SnChatRoom { | class _SnChatRoom implements SnChatRoom { | ||||||
|   const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, required this.isPublic, required this.picture, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final  List<SnChatMember>? members}): _members = members; |   const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, required this.isPublic, required this.isCommunity, required this.picture, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final  List<SnChatMember>? members}): _members = members; | ||||||
|   factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json); |   factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -136,6 +137,7 @@ class _SnChatRoom implements SnChatRoom { | |||||||
| @override final  String? description; | @override final  String? description; | ||||||
| @override final  int type; | @override final  int type; | ||||||
| @override final  bool isPublic; | @override final  bool isPublic; | ||||||
|  | @override final  bool isCommunity; | ||||||
| @override final  SnCloudFile? picture; | @override final  SnCloudFile? picture; | ||||||
| @override final  SnCloudFile? background; | @override final  SnCloudFile? background; | ||||||
| @override final  String? realmId; | @override final  String? realmId; | ||||||
| @@ -166,16 +168,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members)); | int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members)); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; |   return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -186,7 +188,7 @@ abstract mixin class _$SnChatRoomCopyWith<$Res> implements $SnChatRoomCopyWith<$ | |||||||
|   factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl; |   factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members |  String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -203,13 +205,14 @@ class __$SnChatRoomCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatRoom | /// Create a copy of SnChatRoom | ||||||
| /// 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? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) { | ||||||
|   return _then(_SnChatRoom( |   return _then(_SnChatRoom( | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
| as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable | ||||||
| as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable | ||||||
| as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ _SnChatRoom _$SnChatRoomFromJson(Map<String, dynamic> json) => _SnChatRoom( | |||||||
|   description: json['description'] as String?, |   description: json['description'] as String?, | ||||||
|   type: (json['type'] as num).toInt(), |   type: (json['type'] as num).toInt(), | ||||||
|   isPublic: json['is_public'] as bool, |   isPublic: json['is_public'] as bool, | ||||||
|  |   isCommunity: json['is_community'] as bool, | ||||||
|   picture: |   picture: | ||||||
|       json['picture'] == null |       json['picture'] == null | ||||||
|           ? null |           ? null | ||||||
| @@ -44,6 +45,7 @@ Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) => | |||||||
|       'description': instance.description, |       'description': instance.description, | ||||||
|       'type': instance.type, |       'type': instance.type, | ||||||
|       'is_public': instance.isPublic, |       'is_public': instance.isPublic, | ||||||
|  |       'is_community': instance.isCommunity, | ||||||
|       'picture': instance.picture?.toJson(), |       'picture': instance.picture?.toJson(), | ||||||
|       'background': instance.background?.toJson(), |       'background': instance.background?.toJson(), | ||||||
|       'realm_id': instance.realmId, |       'realm_id': instance.realmId, | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ sealed class SnRealm with _$SnRealm { | |||||||
|     required String id, |     required String id, | ||||||
|     required String slug, |     required String slug, | ||||||
|     required String name, |     required String name, | ||||||
|     required String description, |     @Default('') String description, | ||||||
|     required String? verifiedAs, |     required String? verifiedAs, | ||||||
|     required DateTime? verifiedAt, |     required DateTime? verifiedAt, | ||||||
|     required bool isCommunity, |     required bool isCommunity, | ||||||
|   | |||||||
| @@ -117,13 +117,13 @@ $SnCloudFileCopyWith<$Res>? get background { | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnRealm implements SnRealm { | class _SnRealm implements SnRealm { | ||||||
|   const _SnRealm({required this.id, required this.slug, required this.name, required this.description, required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.picture, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}); |   const _SnRealm({required this.id, required this.slug, required this.name, this.description = '', required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.picture, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||||
|   factory _SnRealm.fromJson(Map<String, dynamic> json) => _$SnRealmFromJson(json); |   factory _SnRealm.fromJson(Map<String, dynamic> json) => _$SnRealmFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @override final  String slug; | @override final  String slug; | ||||||
| @override final  String name; | @override final  String name; | ||||||
| @override final  String description; | @override@JsonKey() final  String description; | ||||||
| @override final  String? verifiedAs; | @override final  String? verifiedAs; | ||||||
| @override final  DateTime? verifiedAt; | @override final  DateTime? verifiedAt; | ||||||
| @override final  bool isCommunity; | @override final  bool isCommunity; | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ _SnRealm _$SnRealmFromJson(Map<String, dynamic> json) => _SnRealm( | |||||||
|   id: json['id'] as String, |   id: json['id'] as String, | ||||||
|   slug: json['slug'] as String, |   slug: json['slug'] as String, | ||||||
|   name: json['name'] as String, |   name: json['name'] as String, | ||||||
|   description: json['description'] as String, |   description: json['description'] as String? ?? '', | ||||||
|   verifiedAs: json['verified_as'] as String?, |   verifiedAs: json['verified_as'] as String?, | ||||||
|   verifiedAt: |   verifiedAt: | ||||||
|       json['verified_at'] == null |       json['verified_at'] == null | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ import 'package:island/screens/settings.dart'; | |||||||
| import 'package:island/screens/realm/realms.dart'; | import 'package:island/screens/realm/realms.dart'; | ||||||
| import 'package:island/screens/realm/detail.dart'; | import 'package:island/screens/realm/detail.dart'; | ||||||
| import 'package:island/screens/account/event_calendar.dart'; | import 'package:island/screens/account/event_calendar.dart'; | ||||||
|  | import 'package:island/screens/discovery/realms.dart'; | ||||||
|  |  | ||||||
| // Shell route keys for nested navigation | // Shell route keys for nested navigation | ||||||
| final rootNavigatorKey = GlobalKey<NavigatorState>(); | final rootNavigatorKey = GlobalKey<NavigatorState>(); | ||||||
| @@ -105,10 +106,7 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 builder: (context, state) { |                 builder: (context, state) { | ||||||
|                   final name = state.pathParameters['name']!; |                   final name = state.pathParameters['name']!; | ||||||
|                   final packId = state.pathParameters['packId']!; |                   final packId = state.pathParameters['packId']!; | ||||||
|                   return EditStickerPacksScreen( |                   return EditStickerPacksScreen(pubName: name, packId: packId); | ||||||
|                     pubName: name, |  | ||||||
|                     packId: packId, |  | ||||||
|                   ); |  | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
| @@ -190,6 +188,10 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                       return PublisherProfileScreen(name: name); |                       return PublisherProfileScreen(name: name); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     path: 'discovery/realms', | ||||||
|  |                     builder: (context, state) => const DiscoveryRealmsScreen(), | ||||||
|  |                   ), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|  |  | ||||||
| @@ -198,6 +200,10 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 path: '/chat', |                 path: '/chat', | ||||||
|                 builder: (context, state) => const ChatListScreen(), |                 builder: (context, state) => const ChatListScreen(), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|  |                   GoRoute( | ||||||
|  |                     path: 'new', | ||||||
|  |                     builder: (context, state) => const NewChatScreen(), | ||||||
|  |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: ':id', |                     path: ':id', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
| @@ -205,10 +211,6 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                       return ChatRoomScreen(id: id); |                       return ChatRoomScreen(id: id); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |  | ||||||
|                     path: 'new', |  | ||||||
|                     builder: (context, state) => const NewChatScreen(), |  | ||||||
|                   ), |  | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: ':id/edit', |                     path: ':id/edit', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
| @@ -227,9 +229,9 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|               ), |               ), | ||||||
|  |  | ||||||
|               // Realms tab |               // Realms tab | ||||||
|                GoRoute( |               GoRoute( | ||||||
|                  path: '/realms', |                 path: '/realms', | ||||||
|                  builder: (context, state) => const RealmListScreen(), |                 builder: (context, state) => const RealmListScreen(), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'new', |                     path: 'new', | ||||||
|   | |||||||
| @@ -200,7 +200,7 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|               onTap: () { |               onTap: () { | ||||||
|                 context.push('/notification'); |                 context.push('/account/notifications'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|   | |||||||
| @@ -215,6 +215,7 @@ class RelationshipScreen extends HookConsumerWidget { | |||||||
|     Future<void> addFriend() async { |     Future<void> addFriend() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|  |         useRootNavigator: true, | ||||||
|         builder: (context) => AccountPickerSheet(), |         builder: (context) => AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
| @@ -186,7 +186,7 @@ class ChatShellScreen extends HookConsumerWidget { | |||||||
|         child: Row( |         child: Row( | ||||||
|           children: [ |           children: [ | ||||||
|             Flexible(flex: 2, child: ChatListScreen(isAside: true)), |             Flexible(flex: 2, child: ChatListScreen(isAside: true)), | ||||||
|             VerticalDivider(width: 1), |             const VerticalDivider(width: 1), | ||||||
|             Flexible(flex: 4, child: child), |             Flexible(flex: 4, child: child), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
| @@ -227,7 +227,8 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|     Future<void> createDirectMessage() async { |     Future<void> createDirectMessage() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         builder: (context) => AccountPickerSheet(), |         useRootNavigator: true, | ||||||
|  |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|       final client = ref.read(apiClientProvider); |       final client = ref.read(apiClientProvider); | ||||||
| @@ -242,7 +243,7 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       extendBody: false, // Prevent conflicts with tabs navigation |       extendBody: false, // Prevent conflicts with tabs navigation | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text('chat').tr(), |         title: const Text('chat').tr(), | ||||||
|         bottom: TabBar( |         bottom: TabBar( | ||||||
|           controller: tabController, |           controller: tabController, | ||||||
|           tabs: [ |           tabs: [ | ||||||
| @@ -296,7 +297,7 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|               showModalBottomSheet( |               showModalBottomSheet( | ||||||
|                 isScrollControlled: true, |                 isScrollControlled: true, | ||||||
|                 context: context, |                 context: context, | ||||||
|                 builder: (context) => _ChatInvitesSheet(), |                 builder: (context) => const _ChatInvitesSheet(), | ||||||
|               ); |               ); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
| @@ -307,13 +308,14 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           showModalBottomSheet( |           showModalBottomSheet( | ||||||
|             context: context, |             context: context, | ||||||
|  |             useRootNavigator: true, | ||||||
|             builder: |             builder: | ||||||
|                 (context) => Column( |                 (context) => Column( | ||||||
|                   mainAxisSize: MainAxisSize.min, |                   mainAxisSize: MainAxisSize.min, | ||||||
|                   crossAxisAlignment: CrossAxisAlignment.stretch, |                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     ListTile( |                     ListTile( | ||||||
|                       title: Text('createChatRoom').tr(), |                       title: const Text('createChatRoom').tr(), | ||||||
|                       leading: const Icon(Symbols.add), |                       leading: const Icon(Symbols.add), | ||||||
|                       onTap: () { |                       onTap: () { | ||||||
|                         Navigator.pop(context); |                         Navigator.pop(context); | ||||||
| @@ -325,7 +327,7 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|                       }, |                       }, | ||||||
|                     ), |                     ), | ||||||
|                     ListTile( |                     ListTile( | ||||||
|                       title: Text('createDirectMessage').tr(), |                       title: const Text('createDirectMessage').tr(), | ||||||
|                       leading: const Icon(Symbols.person), |                       leading: const Icon(Symbols.person), | ||||||
|                       onTap: () { |                       onTap: () { | ||||||
|                         Navigator.pop(context); |                         Navigator.pop(context); | ||||||
| @@ -450,7 +452,7 @@ class NewChatScreen extends StatelessWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return EditChatScreen(); |     return const EditChatScreen(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -468,6 +470,8 @@ class EditChatScreen extends HookConsumerWidget { | |||||||
|     final descriptionController = useTextEditingController(); |     final descriptionController = useTextEditingController(); | ||||||
|     final picture = useState<SnCloudFile?>(null); |     final picture = useState<SnCloudFile?>(null); | ||||||
|     final background = useState<SnCloudFile?>(null); |     final background = useState<SnCloudFile?>(null); | ||||||
|  |     final isPublic = useState(true); | ||||||
|  |     final isCommunity = useState(false); | ||||||
|  |  | ||||||
|     final chat = ref.watch(chatroomProvider(id)); |     final chat = ref.watch(chatroomProvider(id)); | ||||||
|  |  | ||||||
| @@ -480,12 +484,14 @@ class EditChatScreen extends HookConsumerWidget { | |||||||
|         descriptionController.text = chat.value!.description ?? ''; |         descriptionController.text = chat.value!.description ?? ''; | ||||||
|         picture.value = chat.value!.picture; |         picture.value = chat.value!.picture; | ||||||
|         background.value = chat.value!.background; |         background.value = chat.value!.background; | ||||||
|  |         isPublic.value = chat.value!.isPublic; | ||||||
|  |         isCommunity.value = chat.value!.isCommunity; | ||||||
|         currentRealm.value = joinedRealms.value?.firstWhereOrNull( |         currentRealm.value = joinedRealms.value?.firstWhereOrNull( | ||||||
|           (realm) => realm.id == chat.value!.realmId, |           (realm) => realm.id == chat.value!.realmId, | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|       return; |       return; | ||||||
|     }, [chat]); |     }, [chat, joinedRealms]); | ||||||
|  |  | ||||||
|     void setPicture(String position) async { |     void setPicture(String position) async { | ||||||
|       showLoadingModal(context); |       showLoadingModal(context); | ||||||
| @@ -503,9 +509,9 @@ class EditChatScreen extends HookConsumerWidget { | |||||||
|         image: result, |         image: result, | ||||||
|         allowedAspectRatios: [ |         allowedAspectRatios: [ | ||||||
|           if (position == 'background') |           if (position == 'background') | ||||||
|             CropAspectRatio(height: 7, width: 16) |             const CropAspectRatio(height: 7, width: 16) | ||||||
|           else |           else | ||||||
|             CropAspectRatio(height: 1, width: 1), |             const CropAspectRatio(height: 1, width: 1), | ||||||
|         ], |         ], | ||||||
|       ); |       ); | ||||||
|       if (result == null) { |       if (result == null) { | ||||||
| @@ -562,6 +568,8 @@ class EditChatScreen extends HookConsumerWidget { | |||||||
|             'background_id': background.value?.id, |             'background_id': background.value?.id, | ||||||
|             'picture_id': picture.value?.id, |             'picture_id': picture.value?.id, | ||||||
|             'realm_id': currentRealm.value?.id, |             'realm_id': currentRealm.value?.id, | ||||||
|  |             'is_public': isPublic.value, | ||||||
|  |             'is_community': isCommunity.value, | ||||||
|           }, |           }, | ||||||
|           options: Options(method: id == null ? 'POST' : 'PATCH'), |           options: Options(method: id == null ? 'POST' : 'PATCH'), | ||||||
|         ); |         ); | ||||||
| @@ -654,6 +662,19 @@ class EditChatScreen extends HookConsumerWidget { | |||||||
|                       (_) => FocusManager.instance.primaryFocus?.unfocus(), |                       (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                 ), |                 ), | ||||||
|                 const SizedBox(height: 16), |                 const SizedBox(height: 16), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   title: const Text('isPublic').tr(), | ||||||
|  |                   subtitle: const Text('isPublicHint').tr(), | ||||||
|  |                   value: isPublic.value, | ||||||
|  |                   onChanged: (value) => isPublic.value = value ?? false, | ||||||
|  |                 ), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   title: const Text('isCommunity').tr(), | ||||||
|  |                   subtitle: const Text('isCommunityHint').tr(), | ||||||
|  |                   value: isCommunity.value, | ||||||
|  |                   onChanged: (value) => isCommunity.value = value ?? false, | ||||||
|  |                 ), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|                 Align( |                 Align( | ||||||
|                   alignment: Alignment.centerRight, |                   alignment: Alignment.centerRight, | ||||||
|                   child: TextButton.icon( |                   child: TextButton.icon( | ||||||
| @@ -754,7 +775,7 @@ class _ChatInvitesSheet extends HookConsumerWidget { | |||||||
|                               ), |                               ), | ||||||
|                               if (invite.chatRoom!.type == 1) |                               if (invite.chatRoom!.type == 1) | ||||||
|                                 Badge( |                                 Badge( | ||||||
|                                   label: Text('directMessage').tr(), |                                   label: const Text('directMessage').tr(), | ||||||
|                                   backgroundColor: |                                   backgroundColor: | ||||||
|                                       Theme.of(context).colorScheme.primary, |                                       Theme.of(context).colorScheme.primary, | ||||||
|                                   textColor: |                                   textColor: | ||||||
|   | |||||||
| @@ -295,6 +295,20 @@ class ChatRoomScreen extends HookConsumerWidget { | |||||||
|   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 chatIdentity = ref.watch(chatroomIdentityProvider(id)); | ||||||
|  |  | ||||||
|  |     if (chatIdentity.isLoading || chatRoom.isLoading) { | ||||||
|  |       return AppScaffold( | ||||||
|  |         appBar: AppBar(leading: const PageBackButton()), | ||||||
|  |         body: CircularProgressIndicator().center(), | ||||||
|  |       ); | ||||||
|  |     } else if (chatIdentity.value == null) { | ||||||
|  |       // Identity was not found, user was not joined | ||||||
|  |       return AppScaffold( | ||||||
|  |         appBar: AppBar(leading: const PageBackButton()), | ||||||
|  |         body: Center(child: Text('You are not a member of this chat room')), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     final messages = ref.watch(messagesNotifierProvider(id)); |     final messages = ref.watch(messagesNotifierProvider(id)); | ||||||
|     final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier); |     final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier); | ||||||
|     final ws = ref.watch(websocketProvider); |     final ws = ref.watch(websocketProvider); | ||||||
|   | |||||||
| @@ -584,8 +584,8 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         isScrollControlled: true, |  | ||||||
|         context: context, |         context: context, | ||||||
|  |         useRootNavigator: true, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								lib/screens/discovery/realms.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/screens/discovery/realms.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/realm/realm_list.dart'; | ||||||
|  |  | ||||||
|  | class DiscoveryRealmsScreen extends HookConsumerWidget { | ||||||
|  |   const DiscoveryRealmsScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: Text('discoverRealms'.tr())), | ||||||
|  |       body: CustomScrollView( | ||||||
|  |         slivers: [ | ||||||
|  |           SliverGap(16), | ||||||
|  |           SliverRealmList(), | ||||||
|  |           SliverGap(MediaQuery.of(context).padding.bottom + 16), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; | |||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/activity.dart'; | import 'package:island/models/activity.dart'; | ||||||
|  | import 'package:island/models/realm.dart'; | ||||||
| import 'package:island/pods/userinfo.dart'; | import 'package:island/pods/userinfo.dart'; | ||||||
| import 'package:island/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| @@ -17,8 +18,8 @@ import 'package:material_symbols_icons/symbols.dart'; | |||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/realm/realm_card.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:island/models/realm.dart'; |  | ||||||
|  |  | ||||||
| part 'explore.g.dart'; | part 'explore.g.dart'; | ||||||
|  |  | ||||||
| @@ -206,7 +207,7 @@ class _DiscoveryActivityItem extends StatelessWidget { | |||||||
|             padding: const EdgeInsets.only(right: 8), |             padding: const EdgeInsets.only(right: 8), | ||||||
|             itemBuilder: (context, index) { |             itemBuilder: (context, index) { | ||||||
|               final realm = items[index]; |               final realm = items[index]; | ||||||
|               return _RealmCard(realm: realm); |               return RealmCard(realm: realm); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
| @@ -215,86 +216,6 @@ class _DiscoveryActivityItem extends StatelessWidget { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class _RealmCard extends ConsumerWidget { |  | ||||||
|   final SnRealm realm; |  | ||||||
|  |  | ||||||
|   const _RealmCard({required this.realm}); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |  | ||||||
|     final client = ref.watch(apiClientProvider); |  | ||||||
|  |  | ||||||
|     Widget imageWidget; |  | ||||||
|     if (realm.picture != null) { |  | ||||||
|       final imageUrl = '${client.options.baseUrl}/files/${realm.picture!.id}'; |  | ||||||
|       imageWidget = Image.network( |  | ||||||
|         imageUrl, |  | ||||||
|         fit: BoxFit.cover, |  | ||||||
|         width: double.infinity, |  | ||||||
|         height: double.infinity, |  | ||||||
|       ); |  | ||||||
|     } else { |  | ||||||
|       imageWidget = Container( |  | ||||||
|         color: Theme.of(context).colorScheme.secondaryContainer, |  | ||||||
|         child: Center( |  | ||||||
|           child: Icon( |  | ||||||
|             Symbols.photo_camera, |  | ||||||
|             color: Theme.of(context).colorScheme.onSecondaryContainer, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return Card( |  | ||||||
|       clipBehavior: Clip.antiAlias, |  | ||||||
|       margin: const EdgeInsets.only(left: 16, bottom: 8, top: 8), |  | ||||||
|       child: InkWell( |  | ||||||
|         onTap: () { |  | ||||||
|           context.push('/realms/${realm.slug}'); |  | ||||||
|         }, |  | ||||||
|         child: ConstrainedBox( |  | ||||||
|           constraints: const BoxConstraints(maxWidth: 280), |  | ||||||
|           child: AspectRatio( |  | ||||||
|             aspectRatio: 16 / 7, |  | ||||||
|             child: Stack( |  | ||||||
|               children: [ |  | ||||||
|                 imageWidget, |  | ||||||
|                 Positioned( |  | ||||||
|                   bottom: 0, |  | ||||||
|                   left: 0, |  | ||||||
|                   right: 0, |  | ||||||
|                   child: Container( |  | ||||||
|                     decoration: BoxDecoration( |  | ||||||
|                       gradient: LinearGradient( |  | ||||||
|                         begin: Alignment.bottomCenter, |  | ||||||
|                         end: Alignment.topCenter, |  | ||||||
|                         colors: [ |  | ||||||
|                           Colors.black.withOpacity(0.7), |  | ||||||
|                           Colors.transparent, |  | ||||||
|                         ], |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                     padding: const EdgeInsets.all(8), |  | ||||||
|                     child: Text( |  | ||||||
|                       realm.name, |  | ||||||
|                       style: Theme.of(context).textTheme.titleSmall?.copyWith( |  | ||||||
|                         color: Colors.white, |  | ||||||
|                         fontWeight: FontWeight.bold, |  | ||||||
|                       ), |  | ||||||
|                       maxLines: 2, |  | ||||||
|                       overflow: TextOverflow.ellipsis, |  | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|               ], |  | ||||||
|             ), |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class _ActivityListView extends HookConsumerWidget { | class _ActivityListView extends HookConsumerWidget { | ||||||
|   final CursorPagingData<SnActivity> data; |   final CursorPagingData<SnActivity> data; | ||||||
|   final int widgetCount; |   final int widgetCount; | ||||||
|   | |||||||
| @@ -1,12 +1,17 @@ | |||||||
| import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:island/screens/chat/chat.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:island/models/chat.dart'; | ||||||
|  | import 'package:island/services/color.dart'; | ||||||
|  | import 'package:palette_generator/palette_generator.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/realm.dart'; | import 'package:island/models/realm.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/screens/realm/realms.dart'; | import 'package:island/screens/realm/realms.dart'; | ||||||
| import 'package:island/widgets/account/account_picker.dart'; | import 'package:island/widgets/account/account_picker.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| @@ -19,11 +24,40 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
|  |  | ||||||
| part 'detail.g.dart'; | part 'detail.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<Color?> realmAppbarForegroundColor(Ref ref, String realmSlug) async { | ||||||
|  |   final realm = await ref.watch(realmProvider(realmSlug).future); | ||||||
|  |   if (realm?.background == null) return null; | ||||||
|  |   final palette = await PaletteGenerator.fromImageProvider( | ||||||
|  |     CloudImageWidget.provider( | ||||||
|  |       fileId: realm!.background!.id, | ||||||
|  |       serverUrl: ref.watch(serverUrlProvider), | ||||||
|  |     ), | ||||||
|  |   ); | ||||||
|  |   final dominantColor = palette.dominantColor?.color; | ||||||
|  |   if (dominantColor == null) return null; | ||||||
|  |   return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; | ||||||
|  | } | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<SnRealmMember?> realmIdentity(Ref ref, String realmSlug) async { | Future<SnRealmMember?> realmIdentity(Ref ref, String realmSlug) async { | ||||||
|  |   try { | ||||||
|  |     final apiClient = ref.watch(apiClientProvider); | ||||||
|  |     final response = await apiClient.get('/realms/$realmSlug/members/me'); | ||||||
|  |     return SnRealmMember.fromJson(response.data); | ||||||
|  |   } catch (err) { | ||||||
|  |     if (err is DioException && err.response?.statusCode == 404) { | ||||||
|  |       return null; // No identity found, user is not a member | ||||||
|  |     } | ||||||
|  |     rethrow; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<SnChatRoom>> realmChatRooms(Ref ref, String realmSlug) async { | ||||||
|   final apiClient = ref.watch(apiClientProvider); |   final apiClient = ref.watch(apiClientProvider); | ||||||
|   final response = await apiClient.get('/realms/$realmSlug/members/me'); |   final response = await apiClient.get('/realms/$realmSlug/chat'); | ||||||
|   return SnRealmMember.fromJson(response.data); |   return (response.data as List).map((e) => SnChatRoom.fromJson(e)).toList(); | ||||||
| } | } | ||||||
|  |  | ||||||
| class RealmDetailScreen extends HookConsumerWidget { | class RealmDetailScreen extends HookConsumerWidget { | ||||||
| @@ -34,9 +68,10 @@ class RealmDetailScreen extends HookConsumerWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final realmState = ref.watch(realmProvider(slug)); |     final realmState = ref.watch(realmProvider(slug)); | ||||||
|  |     final appbarColor = ref.watch(realmAppbarForegroundColorProvider(slug)); | ||||||
|  |  | ||||||
|     const iconShadow = Shadow( |     final iconShadow = Shadow( | ||||||
|       color: Colors.black54, |       color: appbarColor.value?.invert ?? Colors.black54, | ||||||
|       blurRadius: 5.0, |       blurRadius: 5.0, | ||||||
|       offset: Offset(1.0, 1.0), |       offset: Offset(1.0, 1.0), | ||||||
|     ); |     ); | ||||||
| @@ -51,7 +86,11 @@ class RealmDetailScreen extends HookConsumerWidget { | |||||||
|                 SliverAppBar( |                 SliverAppBar( | ||||||
|                   expandedHeight: 180, |                   expandedHeight: 180, | ||||||
|                   pinned: true, |                   pinned: true, | ||||||
|                   leading: PageBackButton(shadows: [iconShadow]), |                   foregroundColor: appbarColor.value, | ||||||
|  |                   leading: PageBackButton( | ||||||
|  |                     color: appbarColor.value, | ||||||
|  |                     shadows: [iconShadow], | ||||||
|  |                   ), | ||||||
|                   flexibleSpace: FlexibleSpaceBar( |                   flexibleSpace: FlexibleSpaceBar( | ||||||
|                     background: |                     background: | ||||||
|                         realm!.background?.id != null |                         realm!.background?.id != null | ||||||
| @@ -63,14 +102,16 @@ class RealmDetailScreen extends HookConsumerWidget { | |||||||
|                     title: Text( |                     title: Text( | ||||||
|                       realm.name, |                       realm.name, | ||||||
|                       style: TextStyle( |                       style: TextStyle( | ||||||
|                         color: Theme.of(context).appBarTheme.foregroundColor, |                         color: | ||||||
|  |                             appbarColor.value ?? | ||||||
|  |                             Theme.of(context).appBarTheme.foregroundColor, | ||||||
|                         shadows: [iconShadow], |                         shadows: [iconShadow], | ||||||
|                       ), |                       ), | ||||||
|                     ), |                     ), | ||||||
|                   ), |                   ), | ||||||
|                   actions: [ |                   actions: [ | ||||||
|                     IconButton( |                     IconButton( | ||||||
|                       icon: const Icon(Icons.people, shadows: [iconShadow]), |                       icon: Icon(Icons.people, shadows: [iconShadow]), | ||||||
|                       onPressed: () { |                       onPressed: () { | ||||||
|                         showModalBottomSheet( |                         showModalBottomSheet( | ||||||
|                           isScrollControlled: true, |                           isScrollControlled: true, | ||||||
| @@ -86,18 +127,97 @@ class RealmDetailScreen extends HookConsumerWidget { | |||||||
|                   ], |                   ], | ||||||
|                 ), |                 ), | ||||||
|                 SliverToBoxAdapter( |                 SliverToBoxAdapter( | ||||||
|                   child: Padding( |                   child: ref | ||||||
|                     padding: const EdgeInsets.all(16.0), |                       .watch(realmIdentityProvider(slug)) | ||||||
|                     child: Column( |                       .when( | ||||||
|                       crossAxisAlignment: CrossAxisAlignment.start, |                         loading: () => const SizedBox.shrink(), | ||||||
|                       children: [ |                         error: (_, _) => const SizedBox.shrink(), | ||||||
|                         Text( |                         data: | ||||||
|                           realm.description, |                             (identity) => Column( | ||||||
|                           style: const TextStyle(fontSize: 16), |                               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|                         ), |                               children: [ | ||||||
|                       ], |                                 ExpansionTile( | ||||||
|                     ), |                                   title: const Text('description').tr(), | ||||||
|                   ), |                                   initiallyExpanded: identity == null, | ||||||
|  |                                   tilePadding: EdgeInsets.symmetric( | ||||||
|  |                                     horizontal: 20, | ||||||
|  |                                   ), | ||||||
|  |                                   expandedCrossAxisAlignment: | ||||||
|  |                                       CrossAxisAlignment.stretch, | ||||||
|  |                                   children: [ | ||||||
|  |                                     Text( | ||||||
|  |                                       realm.description, | ||||||
|  |                                       style: const TextStyle(fontSize: 16), | ||||||
|  |                                     ).padding( | ||||||
|  |                                       horizontal: 20, | ||||||
|  |                                       bottom: 16, | ||||||
|  |                                       top: 8, | ||||||
|  |                                     ), | ||||||
|  |                                   ], | ||||||
|  |                                 ), | ||||||
|  |                                 if (identity == null && realm.isPublic) | ||||||
|  |                                   FilledButton.tonalIcon( | ||||||
|  |                                     onPressed: () async { | ||||||
|  |                                       try { | ||||||
|  |                                         final apiClient = ref.read( | ||||||
|  |                                           apiClientProvider, | ||||||
|  |                                         ); | ||||||
|  |                                         await apiClient.post( | ||||||
|  |                                           '/realms/$slug/members/me', | ||||||
|  |                                         ); | ||||||
|  |                                         ref.invalidate( | ||||||
|  |                                           realmIdentityProvider(slug), | ||||||
|  |                                         ); | ||||||
|  |                                         ref.invalidate(realmsJoinedProvider); | ||||||
|  |                                         showSnackBar('joinRealmSuccess'.tr()); | ||||||
|  |                                       } catch (err) { | ||||||
|  |                                         showErrorAlert(err); | ||||||
|  |                                       } | ||||||
|  |                                     }, | ||||||
|  |                                     icon: const Icon(Symbols.add), | ||||||
|  |                                     label: const Text('joinRealm').tr(), | ||||||
|  |                                   ).padding(horizontal: 16, vertical: 8) | ||||||
|  |                                 else | ||||||
|  |                                   const SizedBox.shrink(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                       ), | ||||||
|  |                 ), | ||||||
|  |                 const SliverToBoxAdapter(child: Divider(height: 1)), | ||||||
|  |                 Consumer( | ||||||
|  |                   builder: (context, ref, _) { | ||||||
|  |                     final chatRooms = ref.watch(realmChatRoomsProvider(slug)); | ||||||
|  |                     return chatRooms.when( | ||||||
|  |                       loading: | ||||||
|  |                           () => const SliverToBoxAdapter( | ||||||
|  |                             child: Center(child: CircularProgressIndicator()), | ||||||
|  |                           ), | ||||||
|  |                       error: | ||||||
|  |                           (error, _) => SliverToBoxAdapter( | ||||||
|  |                             child: Center(child: Text('Error: $error')), | ||||||
|  |                           ), | ||||||
|  |                       data: (rooms) { | ||||||
|  |                         if (rooms.isEmpty) { | ||||||
|  |                           return const SliverToBoxAdapter( | ||||||
|  |                             child: SizedBox.shrink(), | ||||||
|  |                           ); | ||||||
|  |                         } | ||||||
|  |                         return SliverList( | ||||||
|  |                           delegate: SliverChildBuilderDelegate(( | ||||||
|  |                             context, | ||||||
|  |                             index, | ||||||
|  |                           ) { | ||||||
|  |                             return ChatRoomListTile( | ||||||
|  |                               room: rooms[index], | ||||||
|  |                               onTap: () { | ||||||
|  |                                 context.push('/chat/${rooms[index].id}'); | ||||||
|  |                               }, | ||||||
|  |                             ); | ||||||
|  |                           }, childCount: rooms.length), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
| @@ -114,8 +234,8 @@ class _RealmActionMenu extends HookConsumerWidget { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final realmIdentityAsync = ref.watch(realmIdentityProvider(realmSlug)); |     final realmIdentity = ref.watch(realmIdentityProvider(realmSlug)); | ||||||
|     final isModerator = realmIdentityAsync.when( |     final isModerator = realmIdentity.when( | ||||||
|       data: (identity) => (identity?.role ?? 0) >= 50, |       data: (identity) => (identity?.role ?? 0) >= 50, | ||||||
|       loading: () => false, |       loading: () => false, | ||||||
|       error: (_, _) => false, |       error: (_, _) => false, | ||||||
| @@ -141,7 +261,7 @@ class _RealmActionMenu extends HookConsumerWidget { | |||||||
|                   ], |                   ], | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             realmIdentityAsync.when( |             realmIdentity.when( | ||||||
|               data: |               data: | ||||||
|                   (identity) => |                   (identity) => | ||||||
|                       (identity?.role ?? 0) >= 100 |                       (identity?.role ?? 0) >= 100 | ||||||
|   | |||||||
| @@ -6,7 +6,8 @@ part of 'detail.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$realmIdentityHash() => r'eac6e829b5b46bcfadbf201ab6f918d78c894b9f'; | String _$realmAppbarForegroundColorHash() => | ||||||
|  |     r'14b5563d861996ea182d0d2db7aa5c2bb3bbaf48'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -29,6 +30,133 @@ class _SystemHash { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// See also [realmAppbarForegroundColor]. | ||||||
|  | @ProviderFor(realmAppbarForegroundColor) | ||||||
|  | const realmAppbarForegroundColorProvider = RealmAppbarForegroundColorFamily(); | ||||||
|  |  | ||||||
|  | /// See also [realmAppbarForegroundColor]. | ||||||
|  | class RealmAppbarForegroundColorFamily extends Family<AsyncValue<Color?>> { | ||||||
|  |   /// See also [realmAppbarForegroundColor]. | ||||||
|  |   const RealmAppbarForegroundColorFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [realmAppbarForegroundColor]. | ||||||
|  |   RealmAppbarForegroundColorProvider call(String realmSlug) { | ||||||
|  |     return RealmAppbarForegroundColorProvider(realmSlug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   RealmAppbarForegroundColorProvider getProviderOverride( | ||||||
|  |     covariant RealmAppbarForegroundColorProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.realmSlug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'realmAppbarForegroundColorProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [realmAppbarForegroundColor]. | ||||||
|  | class RealmAppbarForegroundColorProvider | ||||||
|  |     extends AutoDisposeFutureProvider<Color?> { | ||||||
|  |   /// See also [realmAppbarForegroundColor]. | ||||||
|  |   RealmAppbarForegroundColorProvider(String realmSlug) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => realmAppbarForegroundColor( | ||||||
|  |           ref as RealmAppbarForegroundColorRef, | ||||||
|  |           realmSlug, | ||||||
|  |         ), | ||||||
|  |         from: realmAppbarForegroundColorProvider, | ||||||
|  |         name: r'realmAppbarForegroundColorProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$realmAppbarForegroundColorHash, | ||||||
|  |         dependencies: RealmAppbarForegroundColorFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             RealmAppbarForegroundColorFamily._allTransitiveDependencies, | ||||||
|  |         realmSlug: realmSlug, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   RealmAppbarForegroundColorProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.realmSlug, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String realmSlug; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<Color?> Function(RealmAppbarForegroundColorRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: RealmAppbarForegroundColorProvider._internal( | ||||||
|  |         (ref) => create(ref as RealmAppbarForegroundColorRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         realmSlug: realmSlug, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<Color?> createElement() { | ||||||
|  |     return _RealmAppbarForegroundColorProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is RealmAppbarForegroundColorProvider && | ||||||
|  |         other.realmSlug == realmSlug; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, realmSlug.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin RealmAppbarForegroundColorRef on AutoDisposeFutureProviderRef<Color?> { | ||||||
|  |   /// The parameter `realmSlug` of this provider. | ||||||
|  |   String get realmSlug; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _RealmAppbarForegroundColorProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<Color?> | ||||||
|  |     with RealmAppbarForegroundColorRef { | ||||||
|  |   _RealmAppbarForegroundColorProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get realmSlug => | ||||||
|  |       (origin as RealmAppbarForegroundColorProvider).realmSlug; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$realmIdentityHash() => r'308d43eef8a6145c762d27bdf7e12e27149524db'; | ||||||
|  |  | ||||||
| /// See also [realmIdentity]. | /// See also [realmIdentity]. | ||||||
| @ProviderFor(realmIdentity) | @ProviderFor(realmIdentity) | ||||||
| const realmIdentityProvider = RealmIdentityFamily(); | const realmIdentityProvider = RealmIdentityFamily(); | ||||||
| @@ -148,6 +276,128 @@ class _RealmIdentityProviderElement | |||||||
|   String get realmSlug => (origin as RealmIdentityProvider).realmSlug; |   String get realmSlug => (origin as RealmIdentityProvider).realmSlug; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | String _$realmChatRoomsHash() => r'8207c1e6f0922323967f208efeed027e943039cc'; | ||||||
|  |  | ||||||
|  | /// See also [realmChatRooms]. | ||||||
|  | @ProviderFor(realmChatRooms) | ||||||
|  | const realmChatRoomsProvider = RealmChatRoomsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [realmChatRooms]. | ||||||
|  | class RealmChatRoomsFamily extends Family<AsyncValue<List<SnChatRoom>>> { | ||||||
|  |   /// See also [realmChatRooms]. | ||||||
|  |   const RealmChatRoomsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [realmChatRooms]. | ||||||
|  |   RealmChatRoomsProvider call(String realmSlug) { | ||||||
|  |     return RealmChatRoomsProvider(realmSlug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   RealmChatRoomsProvider getProviderOverride( | ||||||
|  |     covariant RealmChatRoomsProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.realmSlug); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'realmChatRoomsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [realmChatRooms]. | ||||||
|  | class RealmChatRoomsProvider | ||||||
|  |     extends AutoDisposeFutureProvider<List<SnChatRoom>> { | ||||||
|  |   /// See also [realmChatRooms]. | ||||||
|  |   RealmChatRoomsProvider(String realmSlug) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => realmChatRooms(ref as RealmChatRoomsRef, realmSlug), | ||||||
|  |         from: realmChatRoomsProvider, | ||||||
|  |         name: r'realmChatRoomsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$realmChatRoomsHash, | ||||||
|  |         dependencies: RealmChatRoomsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             RealmChatRoomsFamily._allTransitiveDependencies, | ||||||
|  |         realmSlug: realmSlug, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   RealmChatRoomsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.realmSlug, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String realmSlug; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<SnChatRoom>> Function(RealmChatRoomsRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: RealmChatRoomsProvider._internal( | ||||||
|  |         (ref) => create(ref as RealmChatRoomsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         realmSlug: realmSlug, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<SnChatRoom>> createElement() { | ||||||
|  |     return _RealmChatRoomsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is RealmChatRoomsProvider && other.realmSlug == realmSlug; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, realmSlug.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin RealmChatRoomsRef on AutoDisposeFutureProviderRef<List<SnChatRoom>> { | ||||||
|  |   /// The parameter `realmSlug` of this provider. | ||||||
|  |   String get realmSlug; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _RealmChatRoomsProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<SnChatRoom>> | ||||||
|  |     with RealmChatRoomsRef { | ||||||
|  |   _RealmChatRoomsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get realmSlug => (origin as RealmChatRoomsProvider).realmSlug; | ||||||
|  | } | ||||||
|  |  | ||||||
| String _$realmMemberListNotifierHash() => | String _$realmMemberListNotifierHash() => | ||||||
|     r'b2e3eefc62a597f45df9470b2058fdda62f8853f'; |     r'b2e3eefc62a597f45df9470b2058fdda62f8853f'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,6 +46,10 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: const Text('realms').tr(), |         title: const Text('realms').tr(), | ||||||
|         actions: [ |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.travel_explore), | ||||||
|  |             onPressed: () => context.push('/discovery/realms'), | ||||||
|  |           ), | ||||||
|           IconButton( |           IconButton( | ||||||
|             icon: Badge( |             icon: Badge( | ||||||
|               label: Text( |               label: Text( | ||||||
| @@ -66,7 +70,7 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|               showModalBottomSheet( |               showModalBottomSheet( | ||||||
|                 context: context, |                 context: context, | ||||||
|                 isScrollControlled: true, |                 isScrollControlled: true, | ||||||
|                 builder: (_) => _RealmInviteSheet(), |                 builder: (_) => const _RealmInviteSheet(), | ||||||
|               ); |               ); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
| @@ -74,7 +78,7 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       floatingActionButton: FloatingActionButton( |       floatingActionButton: FloatingActionButton( | ||||||
|         heroTag: Key("realms-page-fab"), |         heroTag: const Key("realms-page-fab"), | ||||||
|         child: const Icon(Symbols.add), |         child: const Icon(Symbols.add), | ||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           context.push('/realms/new').then((value) { |           context.push('/realms/new').then((value) { | ||||||
| @@ -106,7 +110,7 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|                           onTap: () { |                           onTap: () { | ||||||
|                             context.push('/realms/${value[item].slug}'); |                             context.push('/realms/${value[item].slug}'); | ||||||
|                           }, |                           }, | ||||||
|                           contentPadding: EdgeInsets.only( |                           contentPadding: const EdgeInsets.only( | ||||||
|                             left: 16, |                             left: 16, | ||||||
|                             right: 14, |                             right: 14, | ||||||
|                             top: 8, |                             top: 8, | ||||||
| @@ -158,6 +162,8 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final picture = useState<SnCloudFile?>(null); |     final picture = useState<SnCloudFile?>(null); | ||||||
|     final background = useState<SnCloudFile?>(null); |     final background = useState<SnCloudFile?>(null); | ||||||
|  |     final isPublic = useState(true); | ||||||
|  |     final isCommunity = useState(false); | ||||||
|  |  | ||||||
|     final slugController = useTextEditingController(); |     final slugController = useTextEditingController(); | ||||||
|     final nameController = useTextEditingController(); |     final nameController = useTextEditingController(); | ||||||
| @@ -174,6 +180,8 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|         slugController.text = realm.value!.slug; |         slugController.text = realm.value!.slug; | ||||||
|         nameController.text = realm.value!.name; |         nameController.text = realm.value!.name; | ||||||
|         descriptionController.text = realm.value!.description; |         descriptionController.text = realm.value!.description; | ||||||
|  |         isPublic.value = realm.value!.isPublic; | ||||||
|  |         isCommunity.value = realm.value!.isCommunity; | ||||||
|       } |       } | ||||||
|       return null; |       return null; | ||||||
|     }, [realm]); |     }, [realm]); | ||||||
| @@ -194,9 +202,9 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|         image: result, |         image: result, | ||||||
|         allowedAspectRatios: [ |         allowedAspectRatios: [ | ||||||
|           if (position == 'background') |           if (position == 'background') | ||||||
|             CropAspectRatio(height: 7, width: 16) |             const CropAspectRatio(height: 7, width: 16) | ||||||
|           else |           else | ||||||
|             CropAspectRatio(height: 1, width: 1), |             const CropAspectRatio(height: 1, width: 1), | ||||||
|         ], |         ], | ||||||
|       ); |       ); | ||||||
|       if (result == null) { |       if (result == null) { | ||||||
| @@ -252,6 +260,8 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|             'description': descriptionController.text, |             'description': descriptionController.text, | ||||||
|             'background_id': background.value?.id, |             'background_id': background.value?.id, | ||||||
|             'picture_id': picture.value?.id, |             'picture_id': picture.value?.id, | ||||||
|  |             'is_public': isPublic.value, | ||||||
|  |             'is_community': isCommunity.value, | ||||||
|           }, |           }, | ||||||
|           options: Options(method: slug == null ? 'POST' : 'PATCH'), |           options: Options(method: slug == null ? 'POST' : 'PATCH'), | ||||||
|         ); |         ); | ||||||
| @@ -284,9 +294,9 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|                     child: |                     child: | ||||||
|                         background.value != null |                         background.value != null | ||||||
|                             ? CloudFileWidget( |                             ? CloudFileWidget( | ||||||
|                               item: background.value!, |                                 item: background.value!, | ||||||
|                               fit: BoxFit.cover, |                                 fit: BoxFit.cover, | ||||||
|                             ) |                               ) | ||||||
|                             : const SizedBox.shrink(), |                             : const SizedBox.shrink(), | ||||||
|                   ), |                   ), | ||||||
|                   onTap: () { |                   onTap: () { | ||||||
| @@ -314,7 +324,6 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|             key: formKey, |             key: formKey, | ||||||
|             child: Column( |             child: Column( | ||||||
|               crossAxisAlignment: CrossAxisAlignment.start, |               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               spacing: 16, |  | ||||||
|               children: [ |               children: [ | ||||||
|                 TextFormField( |                 TextFormField( | ||||||
|                   controller: slugController, |                   controller: slugController, | ||||||
| @@ -325,12 +334,14 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|                   onTapOutside: |                   onTapOutside: | ||||||
|                       (_) => FocusManager.instance.primaryFocus?.unfocus(), |                       (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                 ), |                 ), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|                 TextFormField( |                 TextFormField( | ||||||
|                   controller: nameController, |                   controller: nameController, | ||||||
|                   decoration: InputDecoration(labelText: 'name'.tr()), |                   decoration: InputDecoration(labelText: 'name'.tr()), | ||||||
|                   onTapOutside: |                   onTapOutside: | ||||||
|                       (_) => FocusManager.instance.primaryFocus?.unfocus(), |                       (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                 ), |                 ), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|                 TextFormField( |                 TextFormField( | ||||||
|                   controller: descriptionController, |                   controller: descriptionController, | ||||||
|                   decoration: InputDecoration(labelText: 'description'.tr()), |                   decoration: InputDecoration(labelText: 'description'.tr()), | ||||||
| @@ -339,6 +350,20 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|                   onTapOutside: |                   onTapOutside: | ||||||
|                       (_) => FocusManager.instance.primaryFocus?.unfocus(), |                       (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                 ), |                 ), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   title: const Text('isPublic').tr(), | ||||||
|  |                   subtitle: const Text('isPublicHint').tr(), | ||||||
|  |                   value: isPublic.value, | ||||||
|  |                   onChanged: (value) => isPublic.value = value ?? false, | ||||||
|  |                 ), | ||||||
|  |                 CheckboxListTile( | ||||||
|  |                   title: const Text('isCommunity').tr(), | ||||||
|  |                   subtitle: const Text('isCommunityHint').tr(), | ||||||
|  |                   value: isCommunity.value, | ||||||
|  |                   onChanged: (value) => isCommunity.value = value ?? false, | ||||||
|  |                 ), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|                 Align( |                 Align( | ||||||
|                   alignment: Alignment.centerRight, |                   alignment: Alignment.centerRight, | ||||||
|                   child: TextButton.icon( |                   child: TextButton.icon( | ||||||
| @@ -410,47 +435,47 @@ class _RealmInviteSheet extends HookConsumerWidget { | |||||||
|             (items) => |             (items) => | ||||||
|                 items.isEmpty |                 items.isEmpty | ||||||
|                     ? Center( |                     ? Center( | ||||||
|                       child: |                         child: | ||||||
|                           Text( |                             Text( | ||||||
|                             'invitesEmpty', |                               'invitesEmpty', | ||||||
|                             textAlign: TextAlign.center, |                               textAlign: TextAlign.center, | ||||||
|                           ).tr(), |                             ).tr(), | ||||||
|                     ) |                       ) | ||||||
|                     : ListView.builder( |                     : ListView.builder( | ||||||
|                       shrinkWrap: true, |                         shrinkWrap: true, | ||||||
|                       itemCount: items.length, |                         itemCount: items.length, | ||||||
|                       itemBuilder: (context, index) { |                         itemBuilder: (context, index) { | ||||||
|                         final invite = items[index]; |                           final invite = items[index]; | ||||||
|                         return ListTile( |                           return ListTile( | ||||||
|                           leading: ProfilePictureWidget( |                             leading: ProfilePictureWidget( | ||||||
|                             fileId: invite.realm!.picture?.id, |                               fileId: invite.realm!.picture?.id, | ||||||
|                             fallbackIcon: Symbols.group, |                               fallbackIcon: Symbols.group, | ||||||
|                           ), |                             ), | ||||||
|                           title: Text(invite.realm!.name), |                             title: Text(invite.realm!.name), | ||||||
|                           subtitle: |                             subtitle: | ||||||
|                               Text( |                                 Text( | ||||||
|                                 invite.role >= 100 |                                   invite.role >= 100 | ||||||
|                                     ? 'permissionOwner' |                                       ? 'permissionOwner' | ||||||
|                                     : invite.role >= 50 |                                       : invite.role >= 50 | ||||||
|                                     ? 'permissionModerator' |                                       ? 'permissionModerator' | ||||||
|                                     : 'permissionMember', |                                       : 'permissionMember', | ||||||
|                               ).tr(), |                                 ).tr(), | ||||||
|                           trailing: Row( |                             trailing: Row( | ||||||
|                             mainAxisSize: MainAxisSize.min, |                               mainAxisSize: MainAxisSize.min, | ||||||
|                             children: [ |                               children: [ | ||||||
|                               IconButton( |                                 IconButton( | ||||||
|                                 icon: const Icon(Symbols.check), |                                   icon: const Icon(Symbols.check), | ||||||
|                                 onPressed: () => acceptInvite(invite), |                                   onPressed: () => acceptInvite(invite), | ||||||
|                               ), |                                 ), | ||||||
|                               IconButton( |                                 IconButton( | ||||||
|                                 icon: const Icon(Symbols.close), |                                   icon: const Icon(Symbols.close), | ||||||
|                                 onPressed: () => declineInvite(invite), |                                   onPressed: () => declineInvite(invite), | ||||||
|                               ), |                                 ), | ||||||
|                             ], |                               ], | ||||||
|                           ), |                             ), | ||||||
|                         ); |                           ); | ||||||
|                       }, |                         }, | ||||||
|                     ), |                       ), | ||||||
|         loading: () => const Center(child: CircularProgressIndicator()), |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|         error: |         error: | ||||||
|             (error, _) => ResponseErrorWidget( |             (error, _) => ResponseErrorWidget( | ||||||
|   | |||||||
| @@ -233,16 +233,27 @@ class MessageItem extends HookConsumerWidget { | |||||||
|                           if (remoteMessage.meta['embeds'] != null) |                           if (remoteMessage.meta['embeds'] != null) | ||||||
|                             ...((remoteMessage.meta['embeds'] as List<dynamic>) |                             ...((remoteMessage.meta['embeds'] as List<dynamic>) | ||||||
|                                 .where((embed) => embed['Type'] == 'link') |                                 .where((embed) => embed['Type'] == 'link') | ||||||
|                                 .map((embed) => SnEmbedLink.fromJson(embed as Map<String, dynamic>)) |                                 .map( | ||||||
|                                 .map((link) => LayoutBuilder( |                                   (embed) => SnEmbedLink.fromJson( | ||||||
|                                       builder: (context, constraints) { |                                     embed as Map<String, dynamic>, | ||||||
|                                         return EmbedLinkWidget( |                                   ), | ||||||
|                                           link: link, |                                 ) | ||||||
|                                           maxWidth: math.min(constraints.maxWidth, 480), |                                 .map( | ||||||
|                                           margin: const EdgeInsets.symmetric(vertical: 4), |                                   (link) => LayoutBuilder( | ||||||
|                                         ); |                                     builder: (context, constraints) { | ||||||
|                                       }, |                                       return EmbedLinkWidget( | ||||||
|                                     )) |                                         link: link, | ||||||
|  |                                         maxWidth: math.min( | ||||||
|  |                                           constraints.maxWidth, | ||||||
|  |                                           480, | ||||||
|  |                                         ), | ||||||
|  |                                         margin: const EdgeInsets.symmetric( | ||||||
|  |                                           vertical: 4, | ||||||
|  |                                         ), | ||||||
|  |                                       ); | ||||||
|  |                                     }, | ||||||
|  |                                   ), | ||||||
|  |                                 ) | ||||||
|                                 .toList()), |                                 .toList()), | ||||||
|                           if (progress != null && progress!.isNotEmpty) |                           if (progress != null && progress!.isNotEmpty) | ||||||
|                             Column( |                             Column( | ||||||
| @@ -482,7 +493,11 @@ class _MessageItemContent extends StatelessWidget { | |||||||
|         ); |         ); | ||||||
|       case 'text': |       case 'text': | ||||||
|       default: |       default: | ||||||
|         return MarkdownTextContent(content: item.content!, isSelectable: true); |         return MarkdownTextContent( | ||||||
|  |           content: item.content!, | ||||||
|  |           isSelectable: true, | ||||||
|  |           linesMargin: EdgeInsets.zero, | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										82
									
								
								lib/widgets/realm/realm_card.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								lib/widgets/realm/realm_card.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/realm.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
|  | class RealmCard extends ConsumerWidget { | ||||||
|  |   final SnRealm realm; | ||||||
|  |  | ||||||
|  |   const RealmCard({super.key, required this.realm}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final client = ref.watch(apiClientProvider); | ||||||
|  |  | ||||||
|  |     Widget imageWidget; | ||||||
|  |     if (realm.picture != null) { | ||||||
|  |       final imageUrl = '${client.options.baseUrl}/files/${realm.picture!.id}'; | ||||||
|  |       imageWidget = Image.network( | ||||||
|  |         imageUrl, | ||||||
|  |         fit: BoxFit.cover, | ||||||
|  |         width: double.infinity, | ||||||
|  |         height: double.infinity, | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       imageWidget = Container( | ||||||
|  |         color: Theme.of(context).colorScheme.secondaryContainer, | ||||||
|  |         child: Center( | ||||||
|  |           child: Icon( | ||||||
|  |             Symbols.photo_camera, | ||||||
|  |             color: Theme.of(context).colorScheme.onSecondaryContainer, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return Card( | ||||||
|  |       clipBehavior: Clip.antiAlias, | ||||||
|  |       child: InkWell( | ||||||
|  |         onTap: () { | ||||||
|  |           context.push('/realms/${realm.slug}'); | ||||||
|  |         }, | ||||||
|  |         child: AspectRatio( | ||||||
|  |           aspectRatio: 16 / 7, | ||||||
|  |           child: Stack( | ||||||
|  |             children: [ | ||||||
|  |               imageWidget, | ||||||
|  |               Positioned( | ||||||
|  |                 bottom: 0, | ||||||
|  |                 left: 0, | ||||||
|  |                 right: 0, | ||||||
|  |                 child: Container( | ||||||
|  |                   decoration: BoxDecoration( | ||||||
|  |                     gradient: LinearGradient( | ||||||
|  |                       begin: Alignment.bottomCenter, | ||||||
|  |                       end: Alignment.topCenter, | ||||||
|  |                       colors: [ | ||||||
|  |                         Colors.black.withOpacity(0.7), | ||||||
|  |                         Colors.transparent, | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   padding: const EdgeInsets.all(8), | ||||||
|  |                   child: Text( | ||||||
|  |                     realm.name, | ||||||
|  |                     style: Theme.of(context).textTheme.titleSmall?.copyWith( | ||||||
|  |                           color: Colors.white, | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                         ), | ||||||
|  |                     maxLines: 2, | ||||||
|  |                     overflow: TextOverflow.ellipsis, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								lib/widgets/realm/realm_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								lib/widgets/realm/realm_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/realm.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/realm/realm_card.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  |  | ||||||
|  | part 'realm_list.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class RealmListNotifier extends _$RealmListNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnRealm> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnRealm>> build() { | ||||||
|  |     return fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnRealm>> fetch({required String? cursor}) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/discovery/realms', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final realms = data.map((json) => SnRealm.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + realms.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + realms.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: realms, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SliverRealmList extends HookConsumerWidget { | ||||||
|  |   const SliverRealmList({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return PagingHelperSliverView( | ||||||
|  |       provider: realmListNotifierProvider, | ||||||
|  |       futureRefreshable: realmListNotifierProvider.future, | ||||||
|  |       notifierRefreshable: realmListNotifierProvider.notifier, | ||||||
|  |       contentBuilder: | ||||||
|  |           (data, widgetCount, endItemView) => SliverList.builder( | ||||||
|  |             itemCount: widgetCount, | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index == widgetCount - 1) { | ||||||
|  |                 return endItemView; | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               final realm = data.items[index]; | ||||||
|  |  | ||||||
|  |               return Padding( | ||||||
|  |                 padding: const EdgeInsets.symmetric( | ||||||
|  |                   horizontal: 16, | ||||||
|  |                   vertical: 8, | ||||||
|  |                 ), | ||||||
|  |                 child: RealmCard(realm: realm), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								lib/widgets/realm/realm_list.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/widgets/realm/realm_list.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'realm_list.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$realmListNotifierHash() => r'440eb8c61db2059699191b904b6518a0b01ccd25'; | ||||||
|  |  | ||||||
|  | /// See also [RealmListNotifier]. | ||||||
|  | @ProviderFor(RealmListNotifier) | ||||||
|  | final realmListNotifierProvider = AutoDisposeAsyncNotifierProvider< | ||||||
|  |   RealmListNotifier, | ||||||
|  |   CursorPagingData<SnRealm> | ||||||
|  | >.internal( | ||||||
|  |   RealmListNotifier.new, | ||||||
|  |   name: r'realmListNotifierProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$realmListNotifierHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | typedef _$RealmListNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnRealm>>; | ||||||
|  | // 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 | ||||||
							
								
								
									
										20
									
								
								lib/widgets/realm/realm_tile.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								lib/widgets/realm/realm_tile.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/realm.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  |  | ||||||
|  | class RealmTile extends HookConsumerWidget { | ||||||
|  |   final SnRealm realm; | ||||||
|  |   const RealmTile({super.key, required this.realm}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return ListTile( | ||||||
|  |       leading: ProfilePictureWidget(file: realm.picture), | ||||||
|  |       title: Text(realm.name), | ||||||
|  |       subtitle: Text(realm.description), | ||||||
|  |       onTap: () => context.push('/realms/${realm.slug}'), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user