Compare commits
	
		
			13 Commits
		
	
	
		
			3.0.0+108
			...
			f03f0181f8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f03f0181f8 | |||
| 6c7d42c31a | |||
| d6c829c26a | |||
| 666a2dfbf5 | |||
| fd979c3a35 | |||
| 847fc6e864 | |||
| 356b7bf01a | |||
| 450d5ebc81 | |||
| f04285848f | |||
| c4becb0a05 | |||
| d22619396b | |||
| fe8640a6db | |||
| ff475d43dd | 
							
								
								
									
										5
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,7 +3,7 @@ name: Build Release | |||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     tags: |     tags: | ||||||
|       - '*' |       - "*" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -59,6 +59,7 @@ jobs: | |||||||
|           sudo apt-get install -y libnotify-dev |           sudo apt-get install -y libnotify-dev | ||||||
|           sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev |           sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev | ||||||
|           sudo apt-get install -y gstreamer-1.0 |           sudo apt-get install -y gstreamer-1.0 | ||||||
|  |           sudo apt-get install -y libsecret-1-0 | ||||||
|       - run: flutter pub get |       - run: flutter pub get | ||||||
|       - run: flutter build linux |       - run: flutter build linux | ||||||
|       - name: Archive production artifacts |       - name: Archive production artifacts | ||||||
| @@ -80,4 +81,4 @@ jobs: | |||||||
|         uses: actions/upload-artifact@v4 |         uses: actions/upload-artifact@v4 | ||||||
|         with: |         with: | ||||||
|           name: build-output-linux-appimage |           name: build-output-linux-appimage | ||||||
|           path: './*.AppImage*' |           path: "./*.AppImage*" | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ | |||||||
|         <receiver |         <receiver | ||||||
|             android:name=".receiver.ReplyReceiver" |             android:name=".receiver.ReplyReceiver" | ||||||
|             android:enabled="true" |             android:enabled="true" | ||||||
|             android:exported="false" /> |             android:exported="true" /> | ||||||
|  |  | ||||||
|         <service |         <service | ||||||
|             android:name=".service.MessagingService" |             android:name=".service.MessagingService" | ||||||
|   | |||||||
| @@ -1,14 +1,39 @@ | |||||||
| package dev.solsynth.solian | package dev.solsynth.solian | ||||||
|  |  | ||||||
|  | import android.content.Intent | ||||||
| import io.flutter.embedding.android.FlutterActivity | import io.flutter.embedding.android.FlutterActivity | ||||||
| import io.flutter.embedding.engine.FlutterEngine | import io.flutter.embedding.engine.FlutterEngine | ||||||
|  | import io.flutter.plugin.common.MethodChannel | ||||||
| import io.flutter.plugins.sharedpreferences.LegacySharedPreferencesPlugin | import io.flutter.plugins.sharedpreferences.LegacySharedPreferencesPlugin | ||||||
|  |  | ||||||
| class MainActivity : FlutterActivity() | class MainActivity : FlutterActivity() | ||||||
| { | { | ||||||
|  |     private val CHANNEL = "dev.solsynth.solian/notifications" | ||||||
|  |  | ||||||
|     override fun configureFlutterEngine(flutterEngine: FlutterEngine) { |     override fun configureFlutterEngine(flutterEngine: FlutterEngine) { | ||||||
|         super.configureFlutterEngine(flutterEngine) |         super.configureFlutterEngine(flutterEngine) | ||||||
|         // https://github.com/flutter/flutter/issues/153075#issuecomment-2693189362 |         // https://github.com/flutter/flutter/issues/153075#issuecomment-2693189362 | ||||||
|         flutterEngine.plugins.add(LegacySharedPreferencesPlugin()) |         flutterEngine.plugins.add(LegacySharedPreferencesPlugin()) | ||||||
|  |  | ||||||
|  |         MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> | ||||||
|  |             if (call.method == "initialLink") { | ||||||
|  |                 val roomId = intent.getStringExtra("room_id") | ||||||
|  |                 if (roomId != null) { | ||||||
|  |                     result.success("/rooms/$roomId") | ||||||
|  |                 } else { | ||||||
|  |                     result.success(null) | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 result.notImplemented() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onNewIntent(intent: Intent) { | ||||||
|  |         super.onNewIntent(intent) | ||||||
|  |         val roomId = intent.getStringExtra("room_id") | ||||||
|  |         if (roomId != null) { | ||||||
|  |             MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL).invokeMethod("newLink", "/rooms/$roomId") | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,48 +1,47 @@ | |||||||
| package dev.solsynth.solian.network | package dev.solsynth.solian.network | ||||||
|  |  | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import okhttp3.* | import android.content.SharedPreferences | ||||||
|  | import okhttp3.Call | ||||||
|  | import okhttp3.Callback | ||||||
| import okhttp3.MediaType.Companion.toMediaType | import okhttp3.MediaType.Companion.toMediaType | ||||||
|  | import okhttp3.OkHttpClient | ||||||
|  | import okhttp3.Request | ||||||
| import okhttp3.RequestBody.Companion.toRequestBody | import okhttp3.RequestBody.Companion.toRequestBody | ||||||
|  | import okhttp3.Response | ||||||
| import org.json.JSONObject | import org.json.JSONObject | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
|  |  | ||||||
| class ApiClient(private val context: Context) { | class ApiClient(private val context: Context) { | ||||||
|     private val client = OkHttpClient() |     private val client = OkHttpClient() | ||||||
|  |     private val sharedPreferences: SharedPreferences = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) | ||||||
|  |  | ||||||
|     fun sendMessage(roomId: String, content: String, repliedMessageId: String, callback: () -> Unit) { |     fun sendMessage(roomId: String, message: String, replyTo: String, callback: (Boolean) -> Unit) { | ||||||
|         val prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) |         val token = sharedPreferences.getString("flutter.token", null) | ||||||
|         val token = prefs.getString("flutter.token", null) |         if (token == null) { | ||||||
|         val serverUrl = prefs.getString("flutter.serverUrl", null) |             callback(false) | ||||||
|  |  | ||||||
|         if (token == null || serverUrl == null) { |  | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         val url = "$serverUrl/chat/$roomId/messages" |         val json = JSONObject().apply { | ||||||
|  |             put("content", message) | ||||||
|         val json = JSONObject() |             put("reply_to", replyTo) | ||||||
|         json.put("content", content) |         } | ||||||
|         json.put("replied_message_id", repliedMessageId) |         val body = json.toString().toRequestBody("application/json; charset=utf-8".toMediaType()) | ||||||
|  |  | ||||||
|         val requestBody = json.toString().toRequestBody("application/json; charset=utf-8".toMediaType()) |  | ||||||
|  |  | ||||||
|         val request = Request.Builder() |         val request = Request.Builder() | ||||||
|             .url(url) |             .url("https://solian.dev/api/rooms/$roomId/messages") | ||||||
|             .post(requestBody) |             .header("Authorization", "Bearer $token") | ||||||
|             .addHeader("Authorization", "AtField $token") |             .post(body) | ||||||
|             .build() |             .build() | ||||||
|  |  | ||||||
|         client.newCall(request).enqueue(object : Callback { |         client.newCall(request).enqueue(object : Callback { | ||||||
|             override fun onFailure(call: Call, e: IOException) { |             override fun onFailure(call: Call, e: IOException) { | ||||||
|                 // Handle failure |                 callback(false) | ||||||
|                 callback() |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             override fun onResponse(call: Call, response: Response) { |             override fun onResponse(call: Call, response: Response) { | ||||||
|                 // Handle success |                 callback(response.isSuccessful) | ||||||
|                 callback() |  | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -72,6 +72,7 @@ class MessagingService: FirebaseMessagingService() { | |||||||
|  |  | ||||||
|         val intent = Intent(this, MainActivity::class.java) |         val intent = Intent(this, MainActivity::class.java) | ||||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) |         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||||||
|  |         intent.putExtra("room_id", roomId) | ||||||
|         val pendingIntent = PendingIntent.getActivity(this, 0, intent, pendingIntentFlags) |         val pendingIntent = PendingIntent.getActivity(this, 0, intent, pendingIntentFlags) | ||||||
|  |  | ||||||
|         val notificationBuilder = NotificationCompat.Builder(this, "messages") |         val notificationBuilder = NotificationCompat.Builder(this, "messages") | ||||||
|   | |||||||
| @@ -89,14 +89,32 @@ | |||||||
|   "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", | ||||||
|   "discover": "Discover", |   "discover": "Discover", | ||||||
|  |   "joinRealm": "Join Realm", | ||||||
|   "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", | ||||||
| @@ -231,6 +249,7 @@ | |||||||
|   "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", | ||||||
| @@ -286,6 +305,12 @@ | |||||||
|   "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?", | ||||||
|  |   "removePublisherMember": "Remove Publisher Member", | ||||||
|  |   "removePublisherMemberHint": "Are you sure to remove this member from the publisher?", | ||||||
|   "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 @{}", | ||||||
| @@ -293,6 +318,10 @@ | |||||||
|   "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", | ||||||
| @@ -304,6 +333,12 @@ | |||||||
|   "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", | ||||||
| @@ -346,6 +381,7 @@ | |||||||
|   "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.", | ||||||
| @@ -373,6 +409,10 @@ | |||||||
|   "lastActiveAt": "Last active at {}", |   "lastActiveAt": "Last active at {}", | ||||||
|   "authDeviceLogout": "Logout", |   "authDeviceLogout": "Logout", | ||||||
|   "authDeviceLogoutHint": "Are you sure you want to logout this device? This will also disable the push notification to this device.", |   "authDeviceLogoutHint": "Are you sure you want to logout this device? This will also disable the push notification to this device.", | ||||||
|  |   "typingHint": { | ||||||
|  |     "one": "{} is typing...", | ||||||
|  |     "other": "{} are typing..." | ||||||
|  |   }, | ||||||
|   "authDeviceEditLabel": "Edit Label", |   "authDeviceEditLabel": "Edit Label", | ||||||
|   "authDeviceLabelTitle": "Edit Device Label", |   "authDeviceLabelTitle": "Edit Device Label", | ||||||
|   "authDeviceLabelHint": "Enter a name for this device", |   "authDeviceLabelHint": "Enter a name for this device", | ||||||
| @@ -439,6 +479,21 @@ | |||||||
|   "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", | ||||||
| @@ -520,17 +575,29 @@ | |||||||
|   "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.", | ||||||
| @@ -564,5 +631,49 @@ | |||||||
|   "realmJoinSuccess": "Successfully joined the realm.", |   "realmJoinSuccess": "Successfully joined the realm.", | ||||||
|   "discoverRealms": "Discover Realms", |   "discoverRealms": "Discover Realms", | ||||||
|   "discoverPublishers": "Discover Publishers", |   "discoverPublishers": "Discover Publishers", | ||||||
|   "search": "Search" |   "search": "Search", | ||||||
|  |   "publisherMembers": "Collaborators", | ||||||
|  |   "developerHub": "Developer Hub", | ||||||
|  |   "developerHubUnselectedHint": "Select a developer to see stats or enroll a new one.", | ||||||
|  |   "enrollDeveloper": "Enroll as a Developer", | ||||||
|  |   "enrollDeveloperHint": "Enroll one of your publishers to become a developer.", | ||||||
|  |   "noPublishersToEnroll": "You don't have any publishers that can be enrolled as a developer.", | ||||||
|  |   "totalCustomApps": "Total Custom Apps", | ||||||
|  |   "customApps": "Custom Apps", | ||||||
|  |   "noCustomApps": "No custom apps yet.", | ||||||
|  |   "createCustomApp": "Create Custom App", | ||||||
|  |   "editCustomApp": "Edit Custom App", | ||||||
|  |   "deleteCustomApp": "Delete Custom App", | ||||||
|  |   "deleteCustomAppHint": "Are you sure you want to delete this custom app? This action cannot be undone.", | ||||||
|  |   "publicRealm": "Public Realm", | ||||||
|  |   "publicRealmDescription": "Anyone can preview the content of this realm.", | ||||||
|  |   "communityRealm": "Community Realm", | ||||||
|  |   "communityRealmDescription": "Anyone can join this realm and participate in discussions. And will show in the discover page & feed.", | ||||||
|  |   "publicChat": "Public Chat", | ||||||
|  |   "publicChatDescription": "Anyone can preview the content of this chat. Including unjoined bots.", | ||||||
|  |   "communityChat": "Community Chat", | ||||||
|  |   "communityChatDescription": "Anyone can join this chat and participate in discussions.", | ||||||
|  |   "appLinks": "App Links", | ||||||
|  |   "homePageUrl": "Home Page URL", | ||||||
|  |   "privacyPolicyUrl": "Privacy Policy URL", | ||||||
|  |   "termsOfServiceUrl": "Terms of Service URL", | ||||||
|  |   "oauthConfig": "OAuth Configuration", | ||||||
|  |   "clientUri": "Client URI", | ||||||
|  |   "redirectUris": "Redirect URIs", | ||||||
|  |   "addRedirectUri": "Add Redirect URI", | ||||||
|  |   "allowedScopes": "Allowed Scopes", | ||||||
|  |   "requirePkce": "Require PKCE", | ||||||
|  |   "allowOfflineAccess": "Allow Offline Access", | ||||||
|  |   "redirectUri": "Redirect URI", | ||||||
|  |   "redirectUriHint": "The redirect URI is used for OAuth authentication. When the app goes to production, we will validate the redirect URI is match your configuration to reject invalid requests.", | ||||||
|  |   "uriRequired": "The URI is required.", | ||||||
|  |   "uriInvalid": "The URI is invalid.", | ||||||
|  |   "add": "Add", | ||||||
|  |   "addScope": "Add Scope", | ||||||
|  |   "scope": "Scope", | ||||||
|  |   "publisherFeatures": "Features", | ||||||
|  |   "publisherFeatureDevelop": "Developer Program", | ||||||
|  |   "publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.", | ||||||
|  |   "publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.", | ||||||
|  |   "learnMore": "Learn More" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import 'package:firebase_core/firebase_core.dart'; | |||||||
| import 'package:firebase_messaging/firebase_messaging.dart'; | import 'package:firebase_messaging/firebase_messaging.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:image_picker_android/image_picker_android.dart'; | import 'package:image_picker_android/image_picker_android.dart'; | ||||||
| @@ -158,6 +159,28 @@ class IslandApp extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|  |       const channel = MethodChannel('dev.solsynth.solian/notifications'); | ||||||
|  |  | ||||||
|  |       Future<void> handleInitialLink() async { | ||||||
|  |         final String? link = await channel.invokeMethod('initialLink'); | ||||||
|  |         if (link != null) { | ||||||
|  |           final router = ref.read(routerProvider); | ||||||
|  |           router.go(link); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (!kIsWeb && Platform.isAndroid) { | ||||||
|  |         handleInitialLink(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       channel.setMethodCallHandler((call) async { | ||||||
|  |         if (call.method == 'newLink') { | ||||||
|  |           final String link = call.arguments; | ||||||
|  |           final router = ref.read(routerProvider); | ||||||
|  |           router.go(link); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |  | ||||||
|       // When the app is opened from a terminated state. |       // When the app is opened from a terminated state. | ||||||
|       FirebaseMessaging.instance.getInitialMessage().then((message) { |       FirebaseMessaging.instance.getInitialMessage().then((message) { | ||||||
|         if (message != null) { |         if (message != null) { | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								lib/models/custom_app.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								lib/models/custom_app.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  | import 'package:island/models/file.dart'; | ||||||
|  | import 'package:island/models/user.dart'; | ||||||
|  |  | ||||||
|  | part 'custom_app.freezed.dart'; | ||||||
|  | part 'custom_app.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class CustomApp with _$CustomApp { | ||||||
|  |   const factory CustomApp({ | ||||||
|  |     @Default('') String id, | ||||||
|  |     @Default('') String slug, | ||||||
|  |     @Default('') String name, | ||||||
|  |     String? description, | ||||||
|  |     @Default(0) int status, | ||||||
|  |     SnCloudFile? picture, | ||||||
|  |     SnCloudFile? background, | ||||||
|  |     SnVerificationMark? verification, | ||||||
|  |     CustomAppOauthConfig? oauthConfig, | ||||||
|  |     CustomAppLinks? links, | ||||||
|  |     @Default([]) List<CustomAppSecret> secrets, | ||||||
|  |     @Default('') String publisherId, | ||||||
|  |   }) = _CustomApp; | ||||||
|  |  | ||||||
|  |   factory CustomApp.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$CustomAppFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class CustomAppLinks with _$CustomAppLinks { | ||||||
|  |   const factory CustomAppLinks({ | ||||||
|  |     String? homePage, | ||||||
|  |     String? privacyPolicy, | ||||||
|  |     String? termsOfService, | ||||||
|  |   }) = _CustomAppLinks; | ||||||
|  |  | ||||||
|  |   factory CustomAppLinks.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$CustomAppLinksFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class CustomAppOauthConfig with _$CustomAppOauthConfig { | ||||||
|  |   const factory CustomAppOauthConfig({ | ||||||
|  |     String? clientUri, | ||||||
|  |     @Default([]) List<String> redirectUris, | ||||||
|  |     List<String>? postLogoutRedirectUris, | ||||||
|  |     @Default(['openid', 'profile', 'email']) List<String> allowedScopes, | ||||||
|  |     @Default(['authorization_code', 'refresh_token']) | ||||||
|  |     List<String> allowedGrantTypes, | ||||||
|  |     @Default(true) bool requirePkce, | ||||||
|  |     @Default(false) bool allowOfflineAccess, | ||||||
|  |   }) = _CustomAppOauthConfig; | ||||||
|  |  | ||||||
|  |   factory CustomAppOauthConfig.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$CustomAppOauthConfigFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class CustomAppSecret with _$CustomAppSecret { | ||||||
|  |   const factory CustomAppSecret({ | ||||||
|  |     @Default('') String id, | ||||||
|  |     @Default('') String secret, | ||||||
|  |     String? description, | ||||||
|  |     DateTime? expiredAt, | ||||||
|  |     @Default(false) bool isOidc, | ||||||
|  |     @Default('') String appId, | ||||||
|  |   }) = _CustomAppSecret; | ||||||
|  |  | ||||||
|  |   factory CustomAppSecret.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$CustomAppSecretFromJson(json); | ||||||
|  | } | ||||||
							
								
								
									
										771
									
								
								lib/models/custom_app.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										771
									
								
								lib/models/custom_app.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,771 @@ | |||||||
|  | // dart format width=80 | ||||||
|  | // coverage:ignore-file | ||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||||
|  |  | ||||||
|  | part of 'custom_app.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // FreezedGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // dart format off | ||||||
|  | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$CustomApp { | ||||||
|  |  | ||||||
|  |  String get id; String get slug; String get name; String? get description; int get status; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; CustomAppOauthConfig? get oauthConfig; CustomAppLinks? get links; List<CustomAppSecret> get secrets; String get publisherId; | ||||||
|  | /// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppCopyWith<CustomApp> get copyWith => _$CustomAppCopyWithImpl<CustomApp>(this as CustomApp, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this CustomApp to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomApp&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.status, status) || other.status == status)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.oauthConfig, oauthConfig) || other.oauthConfig == oauthConfig)&&(identical(other.links, links) || other.links == links)&&const DeepCollectionEquality().equals(other.secrets, secrets)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,slug,name,description,status,picture,background,verification,oauthConfig,links,const DeepCollectionEquality().hash(secrets),publisherId); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomApp(id: $id, slug: $slug, name: $name, description: $description, status: $status, picture: $picture, background: $background, verification: $verification, oauthConfig: $oauthConfig, links: $links, secrets: $secrets, publisherId: $publisherId)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $CustomAppCopyWith<$Res>  { | ||||||
|  |   factory $CustomAppCopyWith(CustomApp value, $Res Function(CustomApp) _then) = _$CustomAppCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String slug, String name, String? description, int status, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, CustomAppOauthConfig? oauthConfig, CustomAppLinks? links, List<CustomAppSecret> secrets, String publisherId | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | $SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnVerificationMarkCopyWith<$Res>? get verification;$CustomAppOauthConfigCopyWith<$Res>? get oauthConfig;$CustomAppLinksCopyWith<$Res>? get links; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$CustomAppCopyWithImpl<$Res> | ||||||
|  |     implements $CustomAppCopyWith<$Res> { | ||||||
|  |   _$CustomAppCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final CustomApp _self; | ||||||
|  |   final $Res Function(CustomApp) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? status = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? oauthConfig = freezed,Object? links = freezed,Object? secrets = null,Object? publisherId = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,name: null == 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?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,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?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnVerificationMark?,oauthConfig: freezed == oauthConfig ? _self.oauthConfig : oauthConfig // ignore: cast_nullable_to_non_nullable | ||||||
|  | as CustomAppOauthConfig?,links: freezed == links ? _self.links : links // ignore: cast_nullable_to_non_nullable | ||||||
|  | as CustomAppLinks?,secrets: null == secrets ? _self.secrets : secrets // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<CustomAppSecret>,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  | /// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get picture { | ||||||
|  |     if (_self.picture == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) { | ||||||
|  |     return _then(_self.copyWith(picture: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get background { | ||||||
|  |     if (_self.background == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||||
|  |     return _then(_self.copyWith(background: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnVerificationMarkCopyWith<$Res>? get verification { | ||||||
|  |     if (_self.verification == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||||
|  |     return _then(_self.copyWith(verification: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppOauthConfigCopyWith<$Res>? get oauthConfig { | ||||||
|  |     if (_self.oauthConfig == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $CustomAppOauthConfigCopyWith<$Res>(_self.oauthConfig!, (value) { | ||||||
|  |     return _then(_self.copyWith(oauthConfig: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppLinksCopyWith<$Res>? get links { | ||||||
|  |     if (_self.links == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $CustomAppLinksCopyWith<$Res>(_self.links!, (value) { | ||||||
|  |     return _then(_self.copyWith(links: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _CustomApp implements CustomApp { | ||||||
|  |   const _CustomApp({this.id = '', this.slug = '', this.name = '', this.description, this.status = 0, this.picture, this.background, this.verification, this.oauthConfig, this.links, final  List<CustomAppSecret> secrets = const [], this.publisherId = ''}): _secrets = secrets; | ||||||
|  |   factory _CustomApp.fromJson(Map<String, dynamic> json) => _$CustomAppFromJson(json); | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  String id; | ||||||
|  | @override@JsonKey() final  String slug; | ||||||
|  | @override@JsonKey() final  String name; | ||||||
|  | @override final  String? description; | ||||||
|  | @override@JsonKey() final  int status; | ||||||
|  | @override final  SnCloudFile? picture; | ||||||
|  | @override final  SnCloudFile? background; | ||||||
|  | @override final  SnVerificationMark? verification; | ||||||
|  | @override final  CustomAppOauthConfig? oauthConfig; | ||||||
|  | @override final  CustomAppLinks? links; | ||||||
|  |  final  List<CustomAppSecret> _secrets; | ||||||
|  | @override@JsonKey() List<CustomAppSecret> get secrets { | ||||||
|  |   if (_secrets is EqualUnmodifiableListView) return _secrets; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_secrets); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  String publisherId; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$CustomAppCopyWith<_CustomApp> get copyWith => __$CustomAppCopyWithImpl<_CustomApp>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$CustomAppToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomApp&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.status, status) || other.status == status)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.oauthConfig, oauthConfig) || other.oauthConfig == oauthConfig)&&(identical(other.links, links) || other.links == links)&&const DeepCollectionEquality().equals(other._secrets, _secrets)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,slug,name,description,status,picture,background,verification,oauthConfig,links,const DeepCollectionEquality().hash(_secrets),publisherId); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomApp(id: $id, slug: $slug, name: $name, description: $description, status: $status, picture: $picture, background: $background, verification: $verification, oauthConfig: $oauthConfig, links: $links, secrets: $secrets, publisherId: $publisherId)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$CustomAppCopyWith<$Res> implements $CustomAppCopyWith<$Res> { | ||||||
|  |   factory _$CustomAppCopyWith(_CustomApp value, $Res Function(_CustomApp) _then) = __$CustomAppCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String slug, String name, String? description, int status, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, CustomAppOauthConfig? oauthConfig, CustomAppLinks? links, List<CustomAppSecret> secrets, String publisherId | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnVerificationMarkCopyWith<$Res>? get verification;@override $CustomAppOauthConfigCopyWith<$Res>? get oauthConfig;@override $CustomAppLinksCopyWith<$Res>? get links; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$CustomAppCopyWithImpl<$Res> | ||||||
|  |     implements _$CustomAppCopyWith<$Res> { | ||||||
|  |   __$CustomAppCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _CustomApp _self; | ||||||
|  |   final $Res Function(_CustomApp) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? status = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? oauthConfig = freezed,Object? links = freezed,Object? secrets = null,Object? publisherId = null,}) { | ||||||
|  |   return _then(_CustomApp( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,name: null == 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?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,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?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnVerificationMark?,oauthConfig: freezed == oauthConfig ? _self.oauthConfig : oauthConfig // ignore: cast_nullable_to_non_nullable | ||||||
|  | as CustomAppOauthConfig?,links: freezed == links ? _self.links : links // ignore: cast_nullable_to_non_nullable | ||||||
|  | as CustomAppLinks?,secrets: null == secrets ? _self._secrets : secrets // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<CustomAppSecret>,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get picture { | ||||||
|  |     if (_self.picture == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) { | ||||||
|  |     return _then(_self.copyWith(picture: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get background { | ||||||
|  |     if (_self.background == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||||
|  |     return _then(_self.copyWith(background: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnVerificationMarkCopyWith<$Res>? get verification { | ||||||
|  |     if (_self.verification == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||||
|  |     return _then(_self.copyWith(verification: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppOauthConfigCopyWith<$Res>? get oauthConfig { | ||||||
|  |     if (_self.oauthConfig == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $CustomAppOauthConfigCopyWith<$Res>(_self.oauthConfig!, (value) { | ||||||
|  |     return _then(_self.copyWith(oauthConfig: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of CustomApp | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppLinksCopyWith<$Res>? get links { | ||||||
|  |     if (_self.links == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $CustomAppLinksCopyWith<$Res>(_self.links!, (value) { | ||||||
|  |     return _then(_self.copyWith(links: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$CustomAppLinks { | ||||||
|  |  | ||||||
|  |  String? get homePage; String? get privacyPolicy; String? get termsOfService; | ||||||
|  | /// Create a copy of CustomAppLinks | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppLinksCopyWith<CustomAppLinks> get copyWith => _$CustomAppLinksCopyWithImpl<CustomAppLinks>(this as CustomAppLinks, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this CustomAppLinks to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomAppLinks&&(identical(other.homePage, homePage) || other.homePage == homePage)&&(identical(other.privacyPolicy, privacyPolicy) || other.privacyPolicy == privacyPolicy)&&(identical(other.termsOfService, termsOfService) || other.termsOfService == termsOfService)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,homePage,privacyPolicy,termsOfService); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppLinks(homePage: $homePage, privacyPolicy: $privacyPolicy, termsOfService: $termsOfService)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $CustomAppLinksCopyWith<$Res>  { | ||||||
|  |   factory $CustomAppLinksCopyWith(CustomAppLinks value, $Res Function(CustomAppLinks) _then) = _$CustomAppLinksCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String? homePage, String? privacyPolicy, String? termsOfService | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$CustomAppLinksCopyWithImpl<$Res> | ||||||
|  |     implements $CustomAppLinksCopyWith<$Res> { | ||||||
|  |   _$CustomAppLinksCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final CustomAppLinks _self; | ||||||
|  |   final $Res Function(CustomAppLinks) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppLinks | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? homePage = freezed,Object? privacyPolicy = freezed,Object? termsOfService = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | homePage: freezed == homePage ? _self.homePage : homePage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,privacyPolicy: freezed == privacyPolicy ? _self.privacyPolicy : privacyPolicy // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,termsOfService: freezed == termsOfService ? _self.termsOfService : termsOfService // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _CustomAppLinks implements CustomAppLinks { | ||||||
|  |   const _CustomAppLinks({this.homePage, this.privacyPolicy, this.termsOfService}); | ||||||
|  |   factory _CustomAppLinks.fromJson(Map<String, dynamic> json) => _$CustomAppLinksFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String? homePage; | ||||||
|  | @override final  String? privacyPolicy; | ||||||
|  | @override final  String? termsOfService; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppLinks | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$CustomAppLinksCopyWith<_CustomAppLinks> get copyWith => __$CustomAppLinksCopyWithImpl<_CustomAppLinks>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$CustomAppLinksToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomAppLinks&&(identical(other.homePage, homePage) || other.homePage == homePage)&&(identical(other.privacyPolicy, privacyPolicy) || other.privacyPolicy == privacyPolicy)&&(identical(other.termsOfService, termsOfService) || other.termsOfService == termsOfService)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,homePage,privacyPolicy,termsOfService); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppLinks(homePage: $homePage, privacyPolicy: $privacyPolicy, termsOfService: $termsOfService)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$CustomAppLinksCopyWith<$Res> implements $CustomAppLinksCopyWith<$Res> { | ||||||
|  |   factory _$CustomAppLinksCopyWith(_CustomAppLinks value, $Res Function(_CustomAppLinks) _then) = __$CustomAppLinksCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String? homePage, String? privacyPolicy, String? termsOfService | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$CustomAppLinksCopyWithImpl<$Res> | ||||||
|  |     implements _$CustomAppLinksCopyWith<$Res> { | ||||||
|  |   __$CustomAppLinksCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _CustomAppLinks _self; | ||||||
|  |   final $Res Function(_CustomAppLinks) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppLinks | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? homePage = freezed,Object? privacyPolicy = freezed,Object? termsOfService = freezed,}) { | ||||||
|  |   return _then(_CustomAppLinks( | ||||||
|  | homePage: freezed == homePage ? _self.homePage : homePage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,privacyPolicy: freezed == privacyPolicy ? _self.privacyPolicy : privacyPolicy // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,termsOfService: freezed == termsOfService ? _self.termsOfService : termsOfService // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$CustomAppOauthConfig { | ||||||
|  |  | ||||||
|  |  String? get clientUri; List<String> get redirectUris; List<String>? get postLogoutRedirectUris; List<String> get allowedScopes; List<String> get allowedGrantTypes; bool get requirePkce; bool get allowOfflineAccess; | ||||||
|  | /// Create a copy of CustomAppOauthConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppOauthConfigCopyWith<CustomAppOauthConfig> get copyWith => _$CustomAppOauthConfigCopyWithImpl<CustomAppOauthConfig>(this as CustomAppOauthConfig, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this CustomAppOauthConfig to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomAppOauthConfig&&(identical(other.clientUri, clientUri) || other.clientUri == clientUri)&&const DeepCollectionEquality().equals(other.redirectUris, redirectUris)&&const DeepCollectionEquality().equals(other.postLogoutRedirectUris, postLogoutRedirectUris)&&const DeepCollectionEquality().equals(other.allowedScopes, allowedScopes)&&const DeepCollectionEquality().equals(other.allowedGrantTypes, allowedGrantTypes)&&(identical(other.requirePkce, requirePkce) || other.requirePkce == requirePkce)&&(identical(other.allowOfflineAccess, allowOfflineAccess) || other.allowOfflineAccess == allowOfflineAccess)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,clientUri,const DeepCollectionEquality().hash(redirectUris),const DeepCollectionEquality().hash(postLogoutRedirectUris),const DeepCollectionEquality().hash(allowedScopes),const DeepCollectionEquality().hash(allowedGrantTypes),requirePkce,allowOfflineAccess); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppOauthConfig(clientUri: $clientUri, redirectUris: $redirectUris, postLogoutRedirectUris: $postLogoutRedirectUris, allowedScopes: $allowedScopes, allowedGrantTypes: $allowedGrantTypes, requirePkce: $requirePkce, allowOfflineAccess: $allowOfflineAccess)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $CustomAppOauthConfigCopyWith<$Res>  { | ||||||
|  |   factory $CustomAppOauthConfigCopyWith(CustomAppOauthConfig value, $Res Function(CustomAppOauthConfig) _then) = _$CustomAppOauthConfigCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String? clientUri, List<String> redirectUris, List<String>? postLogoutRedirectUris, List<String> allowedScopes, List<String> allowedGrantTypes, bool requirePkce, bool allowOfflineAccess | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$CustomAppOauthConfigCopyWithImpl<$Res> | ||||||
|  |     implements $CustomAppOauthConfigCopyWith<$Res> { | ||||||
|  |   _$CustomAppOauthConfigCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final CustomAppOauthConfig _self; | ||||||
|  |   final $Res Function(CustomAppOauthConfig) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppOauthConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? clientUri = freezed,Object? redirectUris = null,Object? postLogoutRedirectUris = freezed,Object? allowedScopes = null,Object? allowedGrantTypes = null,Object? requirePkce = null,Object? allowOfflineAccess = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | clientUri: freezed == clientUri ? _self.clientUri : clientUri // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,redirectUris: null == redirectUris ? _self.redirectUris : redirectUris // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>,postLogoutRedirectUris: freezed == postLogoutRedirectUris ? _self.postLogoutRedirectUris : postLogoutRedirectUris // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>?,allowedScopes: null == allowedScopes ? _self.allowedScopes : allowedScopes // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>,allowedGrantTypes: null == allowedGrantTypes ? _self.allowedGrantTypes : allowedGrantTypes // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>,requirePkce: null == requirePkce ? _self.requirePkce : requirePkce // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,allowOfflineAccess: null == allowOfflineAccess ? _self.allowOfflineAccess : allowOfflineAccess // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _CustomAppOauthConfig implements CustomAppOauthConfig { | ||||||
|  |   const _CustomAppOauthConfig({this.clientUri, final  List<String> redirectUris = const [], final  List<String>? postLogoutRedirectUris, final  List<String> allowedScopes = const ['openid', 'profile', 'email'], final  List<String> allowedGrantTypes = const ['authorization_code', 'refresh_token'], this.requirePkce = true, this.allowOfflineAccess = false}): _redirectUris = redirectUris,_postLogoutRedirectUris = postLogoutRedirectUris,_allowedScopes = allowedScopes,_allowedGrantTypes = allowedGrantTypes; | ||||||
|  |   factory _CustomAppOauthConfig.fromJson(Map<String, dynamic> json) => _$CustomAppOauthConfigFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String? clientUri; | ||||||
|  |  final  List<String> _redirectUris; | ||||||
|  | @override@JsonKey() List<String> get redirectUris { | ||||||
|  |   if (_redirectUris is EqualUnmodifiableListView) return _redirectUris; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_redirectUris); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  final  List<String>? _postLogoutRedirectUris; | ||||||
|  | @override List<String>? get postLogoutRedirectUris { | ||||||
|  |   final value = _postLogoutRedirectUris; | ||||||
|  |   if (value == null) return null; | ||||||
|  |   if (_postLogoutRedirectUris is EqualUnmodifiableListView) return _postLogoutRedirectUris; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  final  List<String> _allowedScopes; | ||||||
|  | @override@JsonKey() List<String> get allowedScopes { | ||||||
|  |   if (_allowedScopes is EqualUnmodifiableListView) return _allowedScopes; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_allowedScopes); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  final  List<String> _allowedGrantTypes; | ||||||
|  | @override@JsonKey() List<String> get allowedGrantTypes { | ||||||
|  |   if (_allowedGrantTypes is EqualUnmodifiableListView) return _allowedGrantTypes; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_allowedGrantTypes); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  bool requirePkce; | ||||||
|  | @override@JsonKey() final  bool allowOfflineAccess; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppOauthConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$CustomAppOauthConfigCopyWith<_CustomAppOauthConfig> get copyWith => __$CustomAppOauthConfigCopyWithImpl<_CustomAppOauthConfig>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$CustomAppOauthConfigToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomAppOauthConfig&&(identical(other.clientUri, clientUri) || other.clientUri == clientUri)&&const DeepCollectionEquality().equals(other._redirectUris, _redirectUris)&&const DeepCollectionEquality().equals(other._postLogoutRedirectUris, _postLogoutRedirectUris)&&const DeepCollectionEquality().equals(other._allowedScopes, _allowedScopes)&&const DeepCollectionEquality().equals(other._allowedGrantTypes, _allowedGrantTypes)&&(identical(other.requirePkce, requirePkce) || other.requirePkce == requirePkce)&&(identical(other.allowOfflineAccess, allowOfflineAccess) || other.allowOfflineAccess == allowOfflineAccess)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,clientUri,const DeepCollectionEquality().hash(_redirectUris),const DeepCollectionEquality().hash(_postLogoutRedirectUris),const DeepCollectionEquality().hash(_allowedScopes),const DeepCollectionEquality().hash(_allowedGrantTypes),requirePkce,allowOfflineAccess); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppOauthConfig(clientUri: $clientUri, redirectUris: $redirectUris, postLogoutRedirectUris: $postLogoutRedirectUris, allowedScopes: $allowedScopes, allowedGrantTypes: $allowedGrantTypes, requirePkce: $requirePkce, allowOfflineAccess: $allowOfflineAccess)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$CustomAppOauthConfigCopyWith<$Res> implements $CustomAppOauthConfigCopyWith<$Res> { | ||||||
|  |   factory _$CustomAppOauthConfigCopyWith(_CustomAppOauthConfig value, $Res Function(_CustomAppOauthConfig) _then) = __$CustomAppOauthConfigCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String? clientUri, List<String> redirectUris, List<String>? postLogoutRedirectUris, List<String> allowedScopes, List<String> allowedGrantTypes, bool requirePkce, bool allowOfflineAccess | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$CustomAppOauthConfigCopyWithImpl<$Res> | ||||||
|  |     implements _$CustomAppOauthConfigCopyWith<$Res> { | ||||||
|  |   __$CustomAppOauthConfigCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _CustomAppOauthConfig _self; | ||||||
|  |   final $Res Function(_CustomAppOauthConfig) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppOauthConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? clientUri = freezed,Object? redirectUris = null,Object? postLogoutRedirectUris = freezed,Object? allowedScopes = null,Object? allowedGrantTypes = null,Object? requirePkce = null,Object? allowOfflineAccess = null,}) { | ||||||
|  |   return _then(_CustomAppOauthConfig( | ||||||
|  | clientUri: freezed == clientUri ? _self.clientUri : clientUri // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,redirectUris: null == redirectUris ? _self._redirectUris : redirectUris // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>,postLogoutRedirectUris: freezed == postLogoutRedirectUris ? _self._postLogoutRedirectUris : postLogoutRedirectUris // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>?,allowedScopes: null == allowedScopes ? _self._allowedScopes : allowedScopes // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>,allowedGrantTypes: null == allowedGrantTypes ? _self._allowedGrantTypes : allowedGrantTypes // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<String>,requirePkce: null == requirePkce ? _self.requirePkce : requirePkce // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,allowOfflineAccess: null == allowOfflineAccess ? _self.allowOfflineAccess : allowOfflineAccess // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$CustomAppSecret { | ||||||
|  |  | ||||||
|  |  String get id; String get secret; String? get description; DateTime? get expiredAt; bool get isOidc; String get appId; | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppSecretCopyWith<CustomAppSecret> get copyWith => _$CustomAppSecretCopyWithImpl<CustomAppSecret>(this as CustomAppSecret, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this CustomAppSecret to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomAppSecret&&(identical(other.id, id) || other.id == id)&&(identical(other.secret, secret) || other.secret == secret)&&(identical(other.description, description) || other.description == description)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.isOidc, isOidc) || other.isOidc == isOidc)&&(identical(other.appId, appId) || other.appId == appId)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,secret,description,expiredAt,isOidc,appId); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppSecret(id: $id, secret: $secret, description: $description, expiredAt: $expiredAt, isOidc: $isOidc, appId: $appId)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $CustomAppSecretCopyWith<$Res>  { | ||||||
|  |   factory $CustomAppSecretCopyWith(CustomAppSecret value, $Res Function(CustomAppSecret) _then) = _$CustomAppSecretCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String secret, String? description, DateTime? expiredAt, bool isOidc, String appId | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$CustomAppSecretCopyWithImpl<$Res> | ||||||
|  |     implements $CustomAppSecretCopyWith<$Res> { | ||||||
|  |   _$CustomAppSecretCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final CustomAppSecret _self; | ||||||
|  |   final $Res Function(CustomAppSecret) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? secret = null,Object? description = freezed,Object? expiredAt = freezed,Object? isOidc = null,Object? appId = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,secret: null == secret ? _self.secret : secret // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,isOidc: null == isOidc ? _self.isOidc : isOidc // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,appId: null == appId ? _self.appId : appId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _CustomAppSecret implements CustomAppSecret { | ||||||
|  |   const _CustomAppSecret({this.id = '', this.secret = '', this.description, this.expiredAt, this.isOidc = false, this.appId = ''}); | ||||||
|  |   factory _CustomAppSecret.fromJson(Map<String, dynamic> json) => _$CustomAppSecretFromJson(json); | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  String id; | ||||||
|  | @override@JsonKey() final  String secret; | ||||||
|  | @override final  String? description; | ||||||
|  | @override final  DateTime? expiredAt; | ||||||
|  | @override@JsonKey() final  bool isOidc; | ||||||
|  | @override@JsonKey() final  String appId; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$CustomAppSecretCopyWith<_CustomAppSecret> get copyWith => __$CustomAppSecretCopyWithImpl<_CustomAppSecret>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$CustomAppSecretToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomAppSecret&&(identical(other.id, id) || other.id == id)&&(identical(other.secret, secret) || other.secret == secret)&&(identical(other.description, description) || other.description == description)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.isOidc, isOidc) || other.isOidc == isOidc)&&(identical(other.appId, appId) || other.appId == appId)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,secret,description,expiredAt,isOidc,appId); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppSecret(id: $id, secret: $secret, description: $description, expiredAt: $expiredAt, isOidc: $isOidc, appId: $appId)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$CustomAppSecretCopyWith<$Res> implements $CustomAppSecretCopyWith<$Res> { | ||||||
|  |   factory _$CustomAppSecretCopyWith(_CustomAppSecret value, $Res Function(_CustomAppSecret) _then) = __$CustomAppSecretCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String secret, String? description, DateTime? expiredAt, bool isOidc, String appId | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$CustomAppSecretCopyWithImpl<$Res> | ||||||
|  |     implements _$CustomAppSecretCopyWith<$Res> { | ||||||
|  |   __$CustomAppSecretCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _CustomAppSecret _self; | ||||||
|  |   final $Res Function(_CustomAppSecret) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? secret = null,Object? description = freezed,Object? expiredAt = freezed,Object? isOidc = null,Object? appId = null,}) { | ||||||
|  |   return _then(_CustomAppSecret( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,secret: null == secret ? _self.secret : secret // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,isOidc: null == isOidc ? _self.isOidc : isOidc // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool,appId: null == appId ? _self.appId : appId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // dart format on | ||||||
							
								
								
									
										137
									
								
								lib/models/custom_app.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								lib/models/custom_app.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'custom_app.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _CustomApp _$CustomAppFromJson(Map<String, dynamic> json) => _CustomApp( | ||||||
|  |   id: json['id'] as String? ?? '', | ||||||
|  |   slug: json['slug'] as String? ?? '', | ||||||
|  |   name: json['name'] as String? ?? '', | ||||||
|  |   description: json['description'] as String?, | ||||||
|  |   status: (json['status'] as num?)?.toInt() ?? 0, | ||||||
|  |   picture: | ||||||
|  |       json['picture'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), | ||||||
|  |   background: | ||||||
|  |       json['background'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnCloudFile.fromJson(json['background'] as Map<String, dynamic>), | ||||||
|  |   verification: | ||||||
|  |       json['verification'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnVerificationMark.fromJson( | ||||||
|  |             json['verification'] as Map<String, dynamic>, | ||||||
|  |           ), | ||||||
|  |   oauthConfig: | ||||||
|  |       json['oauth_config'] == null | ||||||
|  |           ? null | ||||||
|  |           : CustomAppOauthConfig.fromJson( | ||||||
|  |             json['oauth_config'] as Map<String, dynamic>, | ||||||
|  |           ), | ||||||
|  |   links: | ||||||
|  |       json['links'] == null | ||||||
|  |           ? null | ||||||
|  |           : CustomAppLinks.fromJson(json['links'] as Map<String, dynamic>), | ||||||
|  |   secrets: | ||||||
|  |       (json['secrets'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => CustomAppSecret.fromJson(e as Map<String, dynamic>)) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|  |   publisherId: json['publisher_id'] as String? ?? '', | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$CustomAppToJson(_CustomApp instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'slug': instance.slug, | ||||||
|  |       'name': instance.name, | ||||||
|  |       'description': instance.description, | ||||||
|  |       'status': instance.status, | ||||||
|  |       'picture': instance.picture?.toJson(), | ||||||
|  |       'background': instance.background?.toJson(), | ||||||
|  |       'verification': instance.verification?.toJson(), | ||||||
|  |       'oauth_config': instance.oauthConfig?.toJson(), | ||||||
|  |       'links': instance.links?.toJson(), | ||||||
|  |       'secrets': instance.secrets.map((e) => e.toJson()).toList(), | ||||||
|  |       'publisher_id': instance.publisherId, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _CustomAppLinks _$CustomAppLinksFromJson(Map<String, dynamic> json) => | ||||||
|  |     _CustomAppLinks( | ||||||
|  |       homePage: json['home_page'] as String?, | ||||||
|  |       privacyPolicy: json['privacy_policy'] as String?, | ||||||
|  |       termsOfService: json['terms_of_service'] as String?, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$CustomAppLinksToJson(_CustomAppLinks instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'home_page': instance.homePage, | ||||||
|  |       'privacy_policy': instance.privacyPolicy, | ||||||
|  |       'terms_of_service': instance.termsOfService, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _CustomAppOauthConfig _$CustomAppOauthConfigFromJson( | ||||||
|  |   Map<String, dynamic> json, | ||||||
|  | ) => _CustomAppOauthConfig( | ||||||
|  |   clientUri: json['client_uri'] as String?, | ||||||
|  |   redirectUris: | ||||||
|  |       (json['redirect_uris'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|  |   postLogoutRedirectUris: | ||||||
|  |       (json['post_logout_redirect_uris'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList(), | ||||||
|  |   allowedScopes: | ||||||
|  |       (json['allowed_scopes'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList() ?? | ||||||
|  |       const ['openid', 'profile', 'email'], | ||||||
|  |   allowedGrantTypes: | ||||||
|  |       (json['allowed_grant_types'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList() ?? | ||||||
|  |       const ['authorization_code', 'refresh_token'], | ||||||
|  |   requirePkce: json['require_pkce'] as bool? ?? true, | ||||||
|  |   allowOfflineAccess: json['allow_offline_access'] as bool? ?? false, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$CustomAppOauthConfigToJson( | ||||||
|  |   _CustomAppOauthConfig instance, | ||||||
|  | ) => <String, dynamic>{ | ||||||
|  |   'client_uri': instance.clientUri, | ||||||
|  |   'redirect_uris': instance.redirectUris, | ||||||
|  |   'post_logout_redirect_uris': instance.postLogoutRedirectUris, | ||||||
|  |   'allowed_scopes': instance.allowedScopes, | ||||||
|  |   'allowed_grant_types': instance.allowedGrantTypes, | ||||||
|  |   'require_pkce': instance.requirePkce, | ||||||
|  |   'allow_offline_access': instance.allowOfflineAccess, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | _CustomAppSecret _$CustomAppSecretFromJson(Map<String, dynamic> json) => | ||||||
|  |     _CustomAppSecret( | ||||||
|  |       id: json['id'] as String? ?? '', | ||||||
|  |       secret: json['secret'] as String? ?? '', | ||||||
|  |       description: json['description'] as String?, | ||||||
|  |       expiredAt: | ||||||
|  |           json['expired_at'] == null | ||||||
|  |               ? null | ||||||
|  |               : DateTime.parse(json['expired_at'] as String), | ||||||
|  |       isOidc: json['is_oidc'] as bool? ?? false, | ||||||
|  |       appId: json['app_id'] as String? ?? '', | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$CustomAppSecretToJson(_CustomAppSecret instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'secret': instance.secret, | ||||||
|  |       'description': instance.description, | ||||||
|  |       'expired_at': instance.expiredAt?.toIso8601String(), | ||||||
|  |       'is_oidc': instance.isOidc, | ||||||
|  |       'app_id': instance.appId, | ||||||
|  |     }; | ||||||
							
								
								
									
										14
									
								
								lib/models/developer.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								lib/models/developer.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'developer.freezed.dart'; | ||||||
|  | part 'developer.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class DeveloperStats with _$DeveloperStats { | ||||||
|  |   const factory DeveloperStats({ | ||||||
|  |     @Default(0) int totalCustomApps, | ||||||
|  |   }) = _DeveloperStats; | ||||||
|  |  | ||||||
|  |   factory DeveloperStats.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$DeveloperStatsFromJson(json); | ||||||
|  | } | ||||||
							
								
								
									
										148
									
								
								lib/models/developer.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								lib/models/developer.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | |||||||
|  | // dart format width=80 | ||||||
|  | // coverage:ignore-file | ||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||||
|  |  | ||||||
|  | part of 'developer.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // FreezedGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // dart format off | ||||||
|  | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$DeveloperStats { | ||||||
|  |  | ||||||
|  |  int get totalCustomApps; | ||||||
|  | /// Create a copy of DeveloperStats | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $DeveloperStatsCopyWith<DeveloperStats> get copyWith => _$DeveloperStatsCopyWithImpl<DeveloperStats>(this as DeveloperStats, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this DeveloperStats to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is DeveloperStats&&(identical(other.totalCustomApps, totalCustomApps) || other.totalCustomApps == totalCustomApps)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,totalCustomApps); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'DeveloperStats(totalCustomApps: $totalCustomApps)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $DeveloperStatsCopyWith<$Res>  { | ||||||
|  |   factory $DeveloperStatsCopyWith(DeveloperStats value, $Res Function(DeveloperStats) _then) = _$DeveloperStatsCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  int totalCustomApps | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$DeveloperStatsCopyWithImpl<$Res> | ||||||
|  |     implements $DeveloperStatsCopyWith<$Res> { | ||||||
|  |   _$DeveloperStatsCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final DeveloperStats _self; | ||||||
|  |   final $Res Function(DeveloperStats) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of DeveloperStats | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? totalCustomApps = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | totalCustomApps: null == totalCustomApps ? _self.totalCustomApps : totalCustomApps // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _DeveloperStats implements DeveloperStats { | ||||||
|  |   const _DeveloperStats({this.totalCustomApps = 0}); | ||||||
|  |   factory _DeveloperStats.fromJson(Map<String, dynamic> json) => _$DeveloperStatsFromJson(json); | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  int totalCustomApps; | ||||||
|  |  | ||||||
|  | /// Create a copy of DeveloperStats | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$DeveloperStatsCopyWith<_DeveloperStats> get copyWith => __$DeveloperStatsCopyWithImpl<_DeveloperStats>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$DeveloperStatsToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _DeveloperStats&&(identical(other.totalCustomApps, totalCustomApps) || other.totalCustomApps == totalCustomApps)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,totalCustomApps); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'DeveloperStats(totalCustomApps: $totalCustomApps)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$DeveloperStatsCopyWith<$Res> implements $DeveloperStatsCopyWith<$Res> { | ||||||
|  |   factory _$DeveloperStatsCopyWith(_DeveloperStats value, $Res Function(_DeveloperStats) _then) = __$DeveloperStatsCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  int totalCustomApps | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$DeveloperStatsCopyWithImpl<$Res> | ||||||
|  |     implements _$DeveloperStatsCopyWith<$Res> { | ||||||
|  |   __$DeveloperStatsCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _DeveloperStats _self; | ||||||
|  |   final $Res Function(_DeveloperStats) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of DeveloperStats | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? totalCustomApps = null,}) { | ||||||
|  |   return _then(_DeveloperStats( | ||||||
|  | totalCustomApps: null == totalCustomApps ? _self.totalCustomApps : totalCustomApps // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // dart format on | ||||||
							
								
								
									
										15
									
								
								lib/models/developer.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								lib/models/developer.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'developer.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _DeveloperStats _$DeveloperStatsFromJson(Map<String, dynamic> json) => | ||||||
|  |     _DeveloperStats( | ||||||
|  |       totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$DeveloperStatsToJson(_DeveloperStats instance) => | ||||||
|  |     <String, dynamic>{'total_custom_apps': instance.totalCustomApps}; | ||||||
| @@ -2,7 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; | |||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/post_category.dart'; | import 'package:island/models/post_category.dart'; | ||||||
| import 'package:island/models/post_tag.dart'; | import 'package:island/models/post_tag.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/publisher.dart'; | ||||||
|  |  | ||||||
| part 'post.freezed.dart'; | part 'post.freezed.dart'; | ||||||
| part 'post.g.dart'; | part 'post.g.dart'; | ||||||
| @@ -32,7 +32,7 @@ sealed class SnPost with _$SnPost { | |||||||
|     String? forwardedPostId, |     String? forwardedPostId, | ||||||
|     SnPost? forwardedPost, |     SnPost? forwardedPost, | ||||||
|     @Default([]) List<SnCloudFile> attachments, |     @Default([]) List<SnCloudFile> attachments, | ||||||
|     @Default(SnPublisher()) SnPublisher publisher, |     required SnPublisher publisher, | ||||||
|     @Default({}) Map<String, int> reactionsCount, |     @Default({}) Map<String, int> reactionsCount, | ||||||
|     @Default([]) List<dynamic> reactions, |     @Default([]) List<dynamic> reactions, | ||||||
|     @Default([]) List<PostTag> tags, |     @Default([]) List<PostTag> tags, | ||||||
| @@ -47,29 +47,6 @@ sealed class SnPost with _$SnPost { | |||||||
|   factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); |   factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
| @freezed |  | ||||||
| sealed class SnPublisher with _$SnPublisher { |  | ||||||
|   const factory SnPublisher({ |  | ||||||
|     @Default('') String id, |  | ||||||
|     @Default(0) int type, |  | ||||||
|     @Default('') String name, |  | ||||||
|     @Default('') String nick, |  | ||||||
|     @Default('') String bio, |  | ||||||
|     SnCloudFile? picture, |  | ||||||
|     SnCloudFile? background, |  | ||||||
|     SnAccount? account, |  | ||||||
|     String? accountId, |  | ||||||
|     @Default(null) DateTime? createdAt, |  | ||||||
|     @Default(null) DateTime? updatedAt, |  | ||||||
|     DateTime? deletedAt, |  | ||||||
|     String? realmId, |  | ||||||
|     SnVerificationMark? verification, |  | ||||||
|   }) = _SnPublisher; |  | ||||||
|  |  | ||||||
|   factory SnPublisher.fromJson(Map<String, dynamic> json) => |  | ||||||
|       _$SnPublisherFromJson(json); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @freezed | @freezed | ||||||
| sealed class SnPublisherStats with _$SnPublisherStats { | sealed class SnPublisherStats with _$SnPublisherStats { | ||||||
|   const factory SnPublisherStats({ |   const factory SnPublisherStats({ | ||||||
|   | |||||||
| @@ -156,7 +156,7 @@ $SnPublisherCopyWith<$Res> get publisher { | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPost implements SnPost { | class _SnPost implements SnPost { | ||||||
|   const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.type = 0, final  Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, final  List<SnCloudFile> attachments = const [], this.publisher = const SnPublisher(), final  Map<String, int> reactionsCount = const {}, final  List<dynamic> reactions = const [], final  List<PostTag> tags = const [], final  List<PostCategory> categories = const [], final  List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; |   const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.type = 0, final  Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, final  List<SnCloudFile> attachments = const [], required this.publisher, final  Map<String, int> reactionsCount = const {}, final  List<dynamic> reactions = const [], final  List<PostTag> tags = const [], final  List<PostCategory> categories = const [], final  List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; | ||||||
|   factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); |   factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -195,7 +195,7 @@ class _SnPost implements SnPost { | |||||||
|   return EqualUnmodifiableListView(_attachments); |   return EqualUnmodifiableListView(_attachments); | ||||||
| } | } | ||||||
|  |  | ||||||
| @override@JsonKey() final  SnPublisher publisher; | @override final  SnPublisher publisher; | ||||||
|  final  Map<String, int> _reactionsCount; |  final  Map<String, int> _reactionsCount; | ||||||
| @override@JsonKey() Map<String, int> get reactionsCount { | @override@JsonKey() Map<String, int> get reactionsCount { | ||||||
|   if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount; |   if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount; | ||||||
| @@ -373,274 +373,6 @@ $SnPublisherCopyWith<$Res> get publisher { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| mixin _$SnPublisher { |  | ||||||
|  |  | ||||||
|  String get id; int get type; String get name; String get nick; String get bio; SnCloudFile? get picture; SnCloudFile? get background; SnAccount? get account; String? get accountId; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; String? get realmId; SnVerificationMark? get verification; |  | ||||||
| /// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnPublisherCopyWith<SnPublisher> get copyWith => _$SnPublisherCopyWithImpl<SnPublisher>(this as SnPublisher, _$identity); |  | ||||||
|  |  | ||||||
|   /// Serializes this SnPublisher to a JSON map. |  | ||||||
|   Map<String, dynamic> toJson(); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| bool operator ==(Object other) { |  | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @override |  | ||||||
| int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| String toString() { |  | ||||||
|   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)'; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| abstract mixin class $SnPublisherCopyWith<$Res>  { |  | ||||||
|   factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl; |  | ||||||
| @useResult |  | ||||||
| $Res call({ |  | ||||||
|  String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| $SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnAccountCopyWith<$Res>? get account;$SnVerificationMarkCopyWith<$Res>? get verification; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| /// @nodoc |  | ||||||
| class _$SnPublisherCopyWithImpl<$Res> |  | ||||||
|     implements $SnPublisherCopyWith<$Res> { |  | ||||||
|   _$SnPublisherCopyWithImpl(this._self, this._then); |  | ||||||
|  |  | ||||||
|   final SnPublisher _self; |  | ||||||
|   final $Res Function(SnPublisher) _then; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) { |  | ||||||
|   return _then(_self.copyWith( |  | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable |  | ||||||
| as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,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?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable |  | ||||||
| as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable |  | ||||||
| as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable |  | ||||||
| as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable |  | ||||||
| as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable |  | ||||||
| as SnVerificationMark?, |  | ||||||
|   )); |  | ||||||
| } |  | ||||||
| /// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnCloudFileCopyWith<$Res>? get picture { |  | ||||||
|     if (_self.picture == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) { |  | ||||||
|     return _then(_self.copyWith(picture: value)); |  | ||||||
|   }); |  | ||||||
| }/// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnCloudFileCopyWith<$Res>? get background { |  | ||||||
|     if (_self.background == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { |  | ||||||
|     return _then(_self.copyWith(background: value)); |  | ||||||
|   }); |  | ||||||
| }/// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnAccountCopyWith<$Res>? get account { |  | ||||||
|     if (_self.account == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnAccountCopyWith<$Res>(_self.account!, (value) { |  | ||||||
|     return _then(_self.copyWith(account: value)); |  | ||||||
|   }); |  | ||||||
| }/// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnVerificationMarkCopyWith<$Res>? get verification { |  | ||||||
|     if (_self.verification == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { |  | ||||||
|     return _then(_self.copyWith(verification: value)); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| @JsonSerializable() |  | ||||||
|  |  | ||||||
| class _SnPublisher implements SnPublisher { |  | ||||||
|   const _SnPublisher({this.id = '', this.type = 0, this.name = '', this.nick = '', this.bio = '', this.picture, this.background, this.account, this.accountId, this.createdAt = null, this.updatedAt = null, this.deletedAt, this.realmId, this.verification}); |  | ||||||
|   factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json); |  | ||||||
|  |  | ||||||
| @override@JsonKey() final  String id; |  | ||||||
| @override@JsonKey() final  int type; |  | ||||||
| @override@JsonKey() final  String name; |  | ||||||
| @override@JsonKey() final  String nick; |  | ||||||
| @override@JsonKey() final  String bio; |  | ||||||
| @override final  SnCloudFile? picture; |  | ||||||
| @override final  SnCloudFile? background; |  | ||||||
| @override final  SnAccount? account; |  | ||||||
| @override final  String? accountId; |  | ||||||
| @override@JsonKey() final  DateTime? createdAt; |  | ||||||
| @override@JsonKey() final  DateTime? updatedAt; |  | ||||||
| @override final  DateTime? deletedAt; |  | ||||||
| @override final  String? realmId; |  | ||||||
| @override final  SnVerificationMark? verification; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| _$SnPublisherCopyWith<_SnPublisher> get copyWith => __$SnPublisherCopyWithImpl<_SnPublisher>(this, _$identity); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| Map<String, dynamic> toJson() { |  | ||||||
|   return _$SnPublisherToJson(this, ); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| bool operator ==(Object other) { |  | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) |  | ||||||
| @override |  | ||||||
| int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification); |  | ||||||
|  |  | ||||||
| @override |  | ||||||
| String toString() { |  | ||||||
|   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)'; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// @nodoc |  | ||||||
| abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith<$Res> { |  | ||||||
|   factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl; |  | ||||||
| @override @useResult |  | ||||||
| $Res call({ |  | ||||||
|  String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnAccountCopyWith<$Res>? get account;@override $SnVerificationMarkCopyWith<$Res>? get verification; |  | ||||||
|  |  | ||||||
| } |  | ||||||
| /// @nodoc |  | ||||||
| class __$SnPublisherCopyWithImpl<$Res> |  | ||||||
|     implements _$SnPublisherCopyWith<$Res> { |  | ||||||
|   __$SnPublisherCopyWithImpl(this._self, this._then); |  | ||||||
|  |  | ||||||
|   final _SnPublisher _self; |  | ||||||
|   final $Res Function(_SnPublisher) _then; |  | ||||||
|  |  | ||||||
| /// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) { |  | ||||||
|   return _then(_SnPublisher( |  | ||||||
| id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable |  | ||||||
| as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String,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?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable |  | ||||||
| as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable |  | ||||||
| as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable |  | ||||||
| as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable |  | ||||||
| as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable |  | ||||||
| as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable |  | ||||||
| as SnVerificationMark?, |  | ||||||
|   )); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnCloudFileCopyWith<$Res>? get picture { |  | ||||||
|     if (_self.picture == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) { |  | ||||||
|     return _then(_self.copyWith(picture: value)); |  | ||||||
|   }); |  | ||||||
| }/// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnCloudFileCopyWith<$Res>? get background { |  | ||||||
|     if (_self.background == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { |  | ||||||
|     return _then(_self.copyWith(background: value)); |  | ||||||
|   }); |  | ||||||
| }/// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnAccountCopyWith<$Res>? get account { |  | ||||||
|     if (_self.account == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnAccountCopyWith<$Res>(_self.account!, (value) { |  | ||||||
|     return _then(_self.copyWith(account: value)); |  | ||||||
|   }); |  | ||||||
| }/// Create a copy of SnPublisher |  | ||||||
| /// with the given fields replaced by the non-null parameter values. |  | ||||||
| @override |  | ||||||
| @pragma('vm:prefer-inline') |  | ||||||
| $SnVerificationMarkCopyWith<$Res>? get verification { |  | ||||||
|     if (_self.verification == null) { |  | ||||||
|     return null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { |  | ||||||
|     return _then(_self.copyWith(verification: value)); |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPublisherStats { | mixin _$SnPublisherStats { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -48,10 +48,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost( | |||||||
|           ?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>)) | ||||||
|           .toList() ?? |           .toList() ?? | ||||||
|       const [], |       const [], | ||||||
|   publisher: |   publisher: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>), | ||||||
|       json['publisher'] == null |  | ||||||
|           ? const SnPublisher() |  | ||||||
|           : SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>), |  | ||||||
|   reactionsCount: |   reactionsCount: | ||||||
|       (json['reactions_count'] as Map<String, dynamic>?)?.map( |       (json['reactions_count'] as Map<String, dynamic>?)?.map( | ||||||
|         (k, e) => MapEntry(k, (e as num).toInt()), |         (k, e) => MapEntry(k, (e as num).toInt()), | ||||||
| @@ -119,64 +116,6 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ | |||||||
|   'is_truncated': instance.isTruncated, |   'is_truncated': instance.isTruncated, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher( |  | ||||||
|   id: json['id'] as String? ?? '', |  | ||||||
|   type: (json['type'] as num?)?.toInt() ?? 0, |  | ||||||
|   name: json['name'] as String? ?? '', |  | ||||||
|   nick: json['nick'] as String? ?? '', |  | ||||||
|   bio: json['bio'] as String? ?? '', |  | ||||||
|   picture: |  | ||||||
|       json['picture'] == null |  | ||||||
|           ? null |  | ||||||
|           : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), |  | ||||||
|   background: |  | ||||||
|       json['background'] == null |  | ||||||
|           ? null |  | ||||||
|           : SnCloudFile.fromJson(json['background'] as Map<String, dynamic>), |  | ||||||
|   account: |  | ||||||
|       json['account'] == null |  | ||||||
|           ? null |  | ||||||
|           : SnAccount.fromJson(json['account'] as Map<String, dynamic>), |  | ||||||
|   accountId: json['account_id'] as String?, |  | ||||||
|   createdAt: |  | ||||||
|       json['created_at'] == null |  | ||||||
|           ? null |  | ||||||
|           : DateTime.parse(json['created_at'] as String), |  | ||||||
|   updatedAt: |  | ||||||
|       json['updated_at'] == null |  | ||||||
|           ? null |  | ||||||
|           : DateTime.parse(json['updated_at'] as String), |  | ||||||
|   deletedAt: |  | ||||||
|       json['deleted_at'] == null |  | ||||||
|           ? null |  | ||||||
|           : DateTime.parse(json['deleted_at'] as String), |  | ||||||
|   realmId: json['realm_id'] as String?, |  | ||||||
|   verification: |  | ||||||
|       json['verification'] == null |  | ||||||
|           ? null |  | ||||||
|           : SnVerificationMark.fromJson( |  | ||||||
|             json['verification'] as Map<String, dynamic>, |  | ||||||
|           ), |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) => |  | ||||||
|     <String, dynamic>{ |  | ||||||
|       'id': instance.id, |  | ||||||
|       'type': instance.type, |  | ||||||
|       'name': instance.name, |  | ||||||
|       'nick': instance.nick, |  | ||||||
|       'bio': instance.bio, |  | ||||||
|       'picture': instance.picture?.toJson(), |  | ||||||
|       'background': instance.background?.toJson(), |  | ||||||
|       'account': instance.account?.toJson(), |  | ||||||
|       'account_id': instance.accountId, |  | ||||||
|       'created_at': instance.createdAt?.toIso8601String(), |  | ||||||
|       'updated_at': instance.updatedAt?.toIso8601String(), |  | ||||||
|       'deleted_at': instance.deletedAt?.toIso8601String(), |  | ||||||
|       'realm_id': instance.realmId, |  | ||||||
|       'verification': instance.verification?.toJson(), |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| _SnPublisherStats _$SnPublisherStatsFromJson(Map<String, dynamic> json) => | _SnPublisherStats _$SnPublisherStatsFromJson(Map<String, dynamic> json) => | ||||||
|     _SnPublisherStats( |     _SnPublisherStats( | ||||||
|       postsCreated: (json['posts_created'] as num).toInt(), |       postsCreated: (json['posts_created'] as num).toInt(), | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								lib/models/publisher.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								lib/models/publisher.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  | import 'package:island/models/file.dart'; | ||||||
|  | import 'package:island/models/user.dart'; | ||||||
|  |  | ||||||
|  | part 'publisher.freezed.dart'; | ||||||
|  | part 'publisher.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnPublisher with _$SnPublisher { | ||||||
|  |   const factory SnPublisher({ | ||||||
|  |     @Default('') String id, | ||||||
|  |     @Default(0) int type, | ||||||
|  |     @Default('') String name, | ||||||
|  |     @Default('') String nick, | ||||||
|  |     @Default('') String bio, | ||||||
|  |     SnCloudFile? picture, | ||||||
|  |     SnCloudFile? background, | ||||||
|  |     SnAccount? account, | ||||||
|  |     String? accountId, | ||||||
|  |     @Default(null) DateTime? createdAt, | ||||||
|  |     @Default(null) DateTime? updatedAt, | ||||||
|  |     DateTime? deletedAt, | ||||||
|  |     String? realmId, | ||||||
|  |     SnVerificationMark? verification, | ||||||
|  |   }) = _SnPublisher; | ||||||
|  |  | ||||||
|  |   factory SnPublisher.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnPublisherFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnPublisherMember with _$SnPublisherMember { | ||||||
|  |   const factory SnPublisherMember({ | ||||||
|  |     required String publisherId, | ||||||
|  |     required SnPublisher? publisher, | ||||||
|  |     required String accountId, | ||||||
|  |     required SnAccount? account, | ||||||
|  |     required int role, | ||||||
|  |     required DateTime? joinedAt, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnPublisherMember; | ||||||
|  |  | ||||||
|  |   factory SnPublisherMember.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnPublisherMemberFromJson(json); | ||||||
|  | } | ||||||
							
								
								
									
										488
									
								
								lib/models/publisher.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										488
									
								
								lib/models/publisher.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,488 @@ | |||||||
|  | // dart format width=80 | ||||||
|  | // coverage:ignore-file | ||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||||
|  |  | ||||||
|  | part of 'publisher.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // FreezedGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // dart format off | ||||||
|  | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnPublisher { | ||||||
|  |  | ||||||
|  |  String get id; int get type; String get name; String get nick; String get bio; SnCloudFile? get picture; SnCloudFile? get background; SnAccount? get account; String? get accountId; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; String? get realmId; SnVerificationMark? get verification; | ||||||
|  | /// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPublisherCopyWith<SnPublisher> get copyWith => _$SnPublisherCopyWithImpl<SnPublisher>(this as SnPublisher, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnPublisher to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnPublisherCopyWith<$Res>  { | ||||||
|  |   factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | $SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnAccountCopyWith<$Res>? get account;$SnVerificationMarkCopyWith<$Res>? get verification; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnPublisherCopyWithImpl<$Res> | ||||||
|  |     implements $SnPublisherCopyWith<$Res> { | ||||||
|  |   _$SnPublisherCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnPublisher _self; | ||||||
|  |   final $Res Function(SnPublisher) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,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?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnVerificationMark?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  | /// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get picture { | ||||||
|  |     if (_self.picture == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) { | ||||||
|  |     return _then(_self.copyWith(picture: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get background { | ||||||
|  |     if (_self.background == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||||
|  |     return _then(_self.copyWith(background: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountCopyWith<$Res>? get account { | ||||||
|  |     if (_self.account == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||||
|  |     return _then(_self.copyWith(account: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnVerificationMarkCopyWith<$Res>? get verification { | ||||||
|  |     if (_self.verification == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||||
|  |     return _then(_self.copyWith(verification: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnPublisher implements SnPublisher { | ||||||
|  |   const _SnPublisher({this.id = '', this.type = 0, this.name = '', this.nick = '', this.bio = '', this.picture, this.background, this.account, this.accountId, this.createdAt = null, this.updatedAt = null, this.deletedAt, this.realmId, this.verification}); | ||||||
|  |   factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json); | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  String id; | ||||||
|  | @override@JsonKey() final  int type; | ||||||
|  | @override@JsonKey() final  String name; | ||||||
|  | @override@JsonKey() final  String nick; | ||||||
|  | @override@JsonKey() final  String bio; | ||||||
|  | @override final  SnCloudFile? picture; | ||||||
|  | @override final  SnCloudFile? background; | ||||||
|  | @override final  SnAccount? account; | ||||||
|  | @override final  String? accountId; | ||||||
|  | @override@JsonKey() final  DateTime? createdAt; | ||||||
|  | @override@JsonKey() final  DateTime? updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  | @override final  String? realmId; | ||||||
|  | @override final  SnVerificationMark? verification; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnPublisherCopyWith<_SnPublisher> get copyWith => __$SnPublisherCopyWithImpl<_SnPublisher>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnPublisherToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith<$Res> { | ||||||
|  |   factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnAccountCopyWith<$Res>? get account;@override $SnVerificationMarkCopyWith<$Res>? get verification; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnPublisherCopyWithImpl<$Res> | ||||||
|  |     implements _$SnPublisherCopyWith<$Res> { | ||||||
|  |   __$SnPublisherCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnPublisher _self; | ||||||
|  |   final $Res Function(_SnPublisher) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) { | ||||||
|  |   return _then(_SnPublisher( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,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?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnVerificationMark?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get picture { | ||||||
|  |     if (_self.picture == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) { | ||||||
|  |     return _then(_self.copyWith(picture: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnCloudFileCopyWith<$Res>? get background { | ||||||
|  |     if (_self.background == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnCloudFileCopyWith<$Res>(_self.background!, (value) { | ||||||
|  |     return _then(_self.copyWith(background: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountCopyWith<$Res>? get account { | ||||||
|  |     if (_self.account == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||||
|  |     return _then(_self.copyWith(account: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisher | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnVerificationMarkCopyWith<$Res>? get verification { | ||||||
|  |     if (_self.verification == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { | ||||||
|  |     return _then(_self.copyWith(verification: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnPublisherMember { | ||||||
|  |  | ||||||
|  |  String get publisherId; SnPublisher? get publisher; String get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPublisherMemberCopyWith<SnPublisherMember> get copyWith => _$SnPublisherMemberCopyWithImpl<SnPublisherMember>(this as SnPublisherMember, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnPublisherMember to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisherMember&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,publisherId,publisher,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPublisherMember(publisherId: $publisherId, publisher: $publisher, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnPublisherMemberCopyWith<$Res>  { | ||||||
|  |   factory $SnPublisherMemberCopyWith(SnPublisherMember value, $Res Function(SnPublisherMember) _then) = _$SnPublisherMemberCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String publisherId, SnPublisher? publisher, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | $SnPublisherCopyWith<$Res>? get publisher;$SnAccountCopyWith<$Res>? get account; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnPublisherMemberCopyWithImpl<$Res> | ||||||
|  |     implements $SnPublisherMemberCopyWith<$Res> { | ||||||
|  |   _$SnPublisherMemberCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnPublisherMember _self; | ||||||
|  |   final $Res Function(SnPublisherMember) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? publisherId = null,Object? publisher = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnPublisher?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  | /// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPublisherCopyWith<$Res>? get publisher { | ||||||
|  |     if (_self.publisher == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) { | ||||||
|  |     return _then(_self.copyWith(publisher: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountCopyWith<$Res>? get account { | ||||||
|  |     if (_self.account == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||||
|  |     return _then(_self.copyWith(account: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnPublisherMember implements SnPublisherMember { | ||||||
|  |   const _SnPublisherMember({required this.publisherId, required this.publisher, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||||
|  |   factory _SnPublisherMember.fromJson(Map<String, dynamic> json) => _$SnPublisherMemberFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String publisherId; | ||||||
|  | @override final  SnPublisher? publisher; | ||||||
|  | @override final  String accountId; | ||||||
|  | @override final  SnAccount? account; | ||||||
|  | @override final  int role; | ||||||
|  | @override final  DateTime? joinedAt; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnPublisherMemberCopyWith<_SnPublisherMember> get copyWith => __$SnPublisherMemberCopyWithImpl<_SnPublisherMember>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnPublisherMemberToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisherMember&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,publisherId,publisher,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnPublisherMember(publisherId: $publisherId, publisher: $publisher, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnPublisherMemberCopyWith<$Res> implements $SnPublisherMemberCopyWith<$Res> { | ||||||
|  |   factory _$SnPublisherMemberCopyWith(_SnPublisherMember value, $Res Function(_SnPublisherMember) _then) = __$SnPublisherMemberCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String publisherId, SnPublisher? publisher, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override $SnPublisherCopyWith<$Res>? get publisher;@override $SnAccountCopyWith<$Res>? get account; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnPublisherMemberCopyWithImpl<$Res> | ||||||
|  |     implements _$SnPublisherMemberCopyWith<$Res> { | ||||||
|  |   __$SnPublisherMemberCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnPublisherMember _self; | ||||||
|  |   final $Res Function(_SnPublisherMember) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? publisherId = null,Object? publisher = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_SnPublisherMember( | ||||||
|  | publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnPublisher?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnPublisherCopyWith<$Res>? get publisher { | ||||||
|  |     if (_self.publisher == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) { | ||||||
|  |     return _then(_self.copyWith(publisher: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of SnPublisherMember | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountCopyWith<$Res>? get account { | ||||||
|  |     if (_self.account == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnAccountCopyWith<$Res>(_self.account!, (value) { | ||||||
|  |     return _then(_self.copyWith(account: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // dart format on | ||||||
							
								
								
									
										103
									
								
								lib/models/publisher.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/models/publisher.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'publisher.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher( | ||||||
|  |   id: json['id'] as String? ?? '', | ||||||
|  |   type: (json['type'] as num?)?.toInt() ?? 0, | ||||||
|  |   name: json['name'] as String? ?? '', | ||||||
|  |   nick: json['nick'] as String? ?? '', | ||||||
|  |   bio: json['bio'] as String? ?? '', | ||||||
|  |   picture: | ||||||
|  |       json['picture'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), | ||||||
|  |   background: | ||||||
|  |       json['background'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnCloudFile.fromJson(json['background'] as Map<String, dynamic>), | ||||||
|  |   account: | ||||||
|  |       json['account'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnAccount.fromJson(json['account'] as Map<String, dynamic>), | ||||||
|  |   accountId: json['account_id'] as String?, | ||||||
|  |   createdAt: | ||||||
|  |       json['created_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['created_at'] as String), | ||||||
|  |   updatedAt: | ||||||
|  |       json['updated_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['updated_at'] as String), | ||||||
|  |   deletedAt: | ||||||
|  |       json['deleted_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['deleted_at'] as String), | ||||||
|  |   realmId: json['realm_id'] as String?, | ||||||
|  |   verification: | ||||||
|  |       json['verification'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnVerificationMark.fromJson( | ||||||
|  |             json['verification'] as Map<String, dynamic>, | ||||||
|  |           ), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'type': instance.type, | ||||||
|  |       'name': instance.name, | ||||||
|  |       'nick': instance.nick, | ||||||
|  |       'bio': instance.bio, | ||||||
|  |       'picture': instance.picture?.toJson(), | ||||||
|  |       'background': instance.background?.toJson(), | ||||||
|  |       'account': instance.account?.toJson(), | ||||||
|  |       'account_id': instance.accountId, | ||||||
|  |       'created_at': instance.createdAt?.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt?.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |       'realm_id': instance.realmId, | ||||||
|  |       'verification': instance.verification?.toJson(), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _SnPublisherMember _$SnPublisherMemberFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnPublisherMember( | ||||||
|  |       publisherId: json['publisher_id'] as String, | ||||||
|  |       publisher: | ||||||
|  |           json['publisher'] == null | ||||||
|  |               ? null | ||||||
|  |               : SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>), | ||||||
|  |       accountId: json['account_id'] as String, | ||||||
|  |       account: | ||||||
|  |           json['account'] == null | ||||||
|  |               ? null | ||||||
|  |               : SnAccount.fromJson(json['account'] as Map<String, dynamic>), | ||||||
|  |       role: (json['role'] as num).toInt(), | ||||||
|  |       joinedAt: | ||||||
|  |           json['joined_at'] == null | ||||||
|  |               ? null | ||||||
|  |               : DateTime.parse(json['joined_at'] as String), | ||||||
|  |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |       deletedAt: | ||||||
|  |           json['deleted_at'] == null | ||||||
|  |               ? null | ||||||
|  |               : DateTime.parse(json['deleted_at'] as String), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnPublisherMemberToJson(_SnPublisherMember instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'publisher_id': instance.publisherId, | ||||||
|  |       'publisher': instance.publisher?.toJson(), | ||||||
|  |       'account_id': instance.accountId, | ||||||
|  |       'account': instance.account?.toJson(), | ||||||
|  |       'role': instance.role, | ||||||
|  |       'joined_at': instance.joinedAt?.toIso8601String(), | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |     }; | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/publisher.dart'; | ||||||
|  |  | ||||||
| part 'sticker.freezed.dart'; | part 'sticker.freezed.dart'; | ||||||
| part 'sticker.g.dart'; | part 'sticker.g.dart'; | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								lib/models/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								lib/models/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  | import 'package:island/models/embed.dart'; | ||||||
|  |  | ||||||
|  | part 'webfeed.freezed.dart'; | ||||||
|  | part 'webfeed.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class WebFeedConfig with _$WebFeedConfig { | ||||||
|  |   const factory WebFeedConfig({@Default(false) bool scrapPage}) = | ||||||
|  |       _WebFeedConfig; | ||||||
|  |  | ||||||
|  |   factory WebFeedConfig.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$WebFeedConfigFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class WebFeed with _$WebFeed { | ||||||
|  |   const factory WebFeed({ | ||||||
|  |     required String id, | ||||||
|  |     required String url, | ||||||
|  |     required String title, | ||||||
|  |     String? description, | ||||||
|  |     SnScrappedLink? preview, | ||||||
|  |     @Default(WebFeedConfig()) WebFeedConfig config, | ||||||
|  |     required String publisherId, | ||||||
|  |     @Default([]) List<WebArticle> articles, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     DateTime? deletedAt, | ||||||
|  |   }) = _WebFeed; | ||||||
|  |  | ||||||
|  |   factory WebFeed.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$WebFeedFromJson(json); | ||||||
|  |  | ||||||
|  |   factory WebFeed.fromJsonString(String jsonString) => | ||||||
|  |       WebFeed.fromJson(jsonDecode(jsonString) as Map<String, dynamic>); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class WebArticle with _$WebArticle { | ||||||
|  |   const factory WebArticle({ | ||||||
|  |     required String id, | ||||||
|  |     required String title, | ||||||
|  |     required String url, | ||||||
|  |     String? author, | ||||||
|  |     Map<String, dynamic>? meta, | ||||||
|  |     SnScrappedLink? preview, | ||||||
|  |     String? content, | ||||||
|  |     DateTime? publishedAt, | ||||||
|  |     required String feedId, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     DateTime? deletedAt, | ||||||
|  |   }) = _WebArticle; | ||||||
|  |  | ||||||
|  |   factory WebArticle.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$WebArticleFromJson(json); | ||||||
|  |  | ||||||
|  |   factory WebArticle.fromJsonString(String jsonString) => | ||||||
|  |       WebArticle.fromJson(jsonDecode(jsonString) as Map<String, dynamic>); | ||||||
|  | } | ||||||
							
								
								
									
										557
									
								
								lib/models/webfeed.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										557
									
								
								lib/models/webfeed.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,557 @@ | |||||||
|  | // dart format width=80 | ||||||
|  | // coverage:ignore-file | ||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||||
|  |  | ||||||
|  | part of 'webfeed.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // FreezedGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // dart format off | ||||||
|  | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$WebFeedConfig { | ||||||
|  |  | ||||||
|  |  bool get scrapPage; | ||||||
|  | /// Create a copy of WebFeedConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $WebFeedConfigCopyWith<WebFeedConfig> get copyWith => _$WebFeedConfigCopyWithImpl<WebFeedConfig>(this as WebFeedConfig, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this WebFeedConfig to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is WebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,scrapPage); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'WebFeedConfig(scrapPage: $scrapPage)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $WebFeedConfigCopyWith<$Res>  { | ||||||
|  |   factory $WebFeedConfigCopyWith(WebFeedConfig value, $Res Function(WebFeedConfig) _then) = _$WebFeedConfigCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  bool scrapPage | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$WebFeedConfigCopyWithImpl<$Res> | ||||||
|  |     implements $WebFeedConfigCopyWith<$Res> { | ||||||
|  |   _$WebFeedConfigCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final WebFeedConfig _self; | ||||||
|  |   final $Res Function(WebFeedConfig) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebFeedConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? scrapPage = null,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | scrapPage: null == scrapPage ? _self.scrapPage : scrapPage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _WebFeedConfig implements WebFeedConfig { | ||||||
|  |   const _WebFeedConfig({this.scrapPage = false}); | ||||||
|  |   factory _WebFeedConfig.fromJson(Map<String, dynamic> json) => _$WebFeedConfigFromJson(json); | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  bool scrapPage; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebFeedConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$WebFeedConfigCopyWith<_WebFeedConfig> get copyWith => __$WebFeedConfigCopyWithImpl<_WebFeedConfig>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$WebFeedConfigToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,scrapPage); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'WebFeedConfig(scrapPage: $scrapPage)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$WebFeedConfigCopyWith<$Res> implements $WebFeedConfigCopyWith<$Res> { | ||||||
|  |   factory _$WebFeedConfigCopyWith(_WebFeedConfig value, $Res Function(_WebFeedConfig) _then) = __$WebFeedConfigCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  bool scrapPage | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$WebFeedConfigCopyWithImpl<$Res> | ||||||
|  |     implements _$WebFeedConfigCopyWith<$Res> { | ||||||
|  |   __$WebFeedConfigCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _WebFeedConfig _self; | ||||||
|  |   final $Res Function(_WebFeedConfig) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebFeedConfig | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? scrapPage = null,}) { | ||||||
|  |   return _then(_WebFeedConfig( | ||||||
|  | scrapPage: null == scrapPage ? _self.scrapPage : scrapPage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$WebFeed { | ||||||
|  |  | ||||||
|  |  String get id; String get url; String get title; String? get description; SnScrappedLink? get preview; WebFeedConfig get config; String get publisherId; List<WebArticle> get articles; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $WebFeedCopyWith<WebFeed> get copyWith => _$WebFeedCopyWithImpl<WebFeed>(this as WebFeed, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this WebFeed to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is WebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other.articles, articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,url,title,description,preview,config,publisherId,const DeepCollectionEquality().hash(articles),createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'WebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $WebFeedCopyWith<$Res>  { | ||||||
|  |   factory $WebFeedCopyWith(WebFeed value, $Res Function(WebFeed) _then) = _$WebFeedCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String url, String title, String? description, SnScrappedLink? preview, WebFeedConfig config, String publisherId, List<WebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | $SnScrappedLinkCopyWith<$Res>? get preview;$WebFeedConfigCopyWith<$Res> get config; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$WebFeedCopyWithImpl<$Res> | ||||||
|  |     implements $WebFeedCopyWith<$Res> { | ||||||
|  |   _$WebFeedCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final WebFeed _self; | ||||||
|  |   final $Res Function(WebFeed) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable | ||||||
|  | as WebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,articles: null == articles ? _self.articles : articles // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<WebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  | /// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnScrappedLinkCopyWith<$Res>? get preview { | ||||||
|  |     if (_self.preview == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) { | ||||||
|  |     return _then(_self.copyWith(preview: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $WebFeedConfigCopyWith<$Res> get config { | ||||||
|  |    | ||||||
|  |   return $WebFeedConfigCopyWith<$Res>(_self.config, (value) { | ||||||
|  |     return _then(_self.copyWith(config: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _WebFeed implements WebFeed { | ||||||
|  |   const _WebFeed({required this.id, required this.url, required this.title, this.description, this.preview, this.config = const WebFeedConfig(), required this.publisherId, final  List<WebArticle> articles = const [], required this.createdAt, required this.updatedAt, this.deletedAt}): _articles = articles; | ||||||
|  |   factory _WebFeed.fromJson(Map<String, dynamic> json) => _$WebFeedFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  String url; | ||||||
|  | @override final  String title; | ||||||
|  | @override final  String? description; | ||||||
|  | @override final  SnScrappedLink? preview; | ||||||
|  | @override@JsonKey() final  WebFeedConfig config; | ||||||
|  | @override final  String publisherId; | ||||||
|  |  final  List<WebArticle> _articles; | ||||||
|  | @override@JsonKey() List<WebArticle> get articles { | ||||||
|  |   if (_articles is EqualUnmodifiableListView) return _articles; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableListView(_articles); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$WebFeedCopyWith<_WebFeed> get copyWith => __$WebFeedCopyWithImpl<_WebFeed>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$WebFeedToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other._articles, _articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,url,title,description,preview,config,publisherId,const DeepCollectionEquality().hash(_articles),createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'WebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$WebFeedCopyWith<$Res> implements $WebFeedCopyWith<$Res> { | ||||||
|  |   factory _$WebFeedCopyWith(_WebFeed value, $Res Function(_WebFeed) _then) = __$WebFeedCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String url, String title, String? description, SnScrappedLink? preview, WebFeedConfig config, String publisherId, List<WebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override $SnScrappedLinkCopyWith<$Res>? get preview;@override $WebFeedConfigCopyWith<$Res> get config; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$WebFeedCopyWithImpl<$Res> | ||||||
|  |     implements _$WebFeedCopyWith<$Res> { | ||||||
|  |   __$WebFeedCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _WebFeed _self; | ||||||
|  |   final $Res Function(_WebFeed) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_WebFeed( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable | ||||||
|  | as WebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,articles: null == articles ? _self._articles : articles // ignore: cast_nullable_to_non_nullable | ||||||
|  | as List<WebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnScrappedLinkCopyWith<$Res>? get preview { | ||||||
|  |     if (_self.preview == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) { | ||||||
|  |     return _then(_self.copyWith(preview: value)); | ||||||
|  |   }); | ||||||
|  | }/// Create a copy of WebFeed | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $WebFeedConfigCopyWith<$Res> get config { | ||||||
|  |    | ||||||
|  |   return $WebFeedConfigCopyWith<$Res>(_self.config, (value) { | ||||||
|  |     return _then(_self.copyWith(config: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$WebArticle { | ||||||
|  |  | ||||||
|  |  String get id; String get title; String get url; String? get author; Map<String, dynamic>? get meta; SnScrappedLink? get preview; String? get content; DateTime? get publishedAt; String get feedId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of WebArticle | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $WebArticleCopyWith<WebArticle> get copyWith => _$WebArticleCopyWithImpl<WebArticle>(this as WebArticle, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this WebArticle to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is WebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(meta),preview,content,publishedAt,feedId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'WebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $WebArticleCopyWith<$Res>  { | ||||||
|  |   factory $WebArticleCopyWith(WebArticle value, $Res Function(WebArticle) _then) = _$WebArticleCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | $SnScrappedLinkCopyWith<$Res>? get preview; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$WebArticleCopyWithImpl<$Res> | ||||||
|  |     implements $WebArticleCopyWith<$Res> { | ||||||
|  |   _$WebArticleCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final WebArticle _self; | ||||||
|  |   final $Res Function(WebArticle) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebArticle | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnScrappedLink?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  | /// Create a copy of WebArticle | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnScrappedLinkCopyWith<$Res>? get preview { | ||||||
|  |     if (_self.preview == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) { | ||||||
|  |     return _then(_self.copyWith(preview: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _WebArticle implements WebArticle { | ||||||
|  |   const _WebArticle({required this.id, required this.title, required this.url, this.author, final  Map<String, dynamic>? meta, this.preview, this.content, this.publishedAt, required this.feedId, required this.createdAt, required this.updatedAt, this.deletedAt}): _meta = meta; | ||||||
|  |   factory _WebArticle.fromJson(Map<String, dynamic> json) => _$WebArticleFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  String title; | ||||||
|  | @override final  String url; | ||||||
|  | @override final  String? author; | ||||||
|  |  final  Map<String, dynamic>? _meta; | ||||||
|  | @override Map<String, dynamic>? get meta { | ||||||
|  |   final value = _meta; | ||||||
|  |   if (value == null) return null; | ||||||
|  |   if (_meta is EqualUnmodifiableMapView) return _meta; | ||||||
|  |   // ignore: implicit_dynamic_type | ||||||
|  |   return EqualUnmodifiableMapView(value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override final  SnScrappedLink? preview; | ||||||
|  | @override final  String? content; | ||||||
|  | @override final  DateTime? publishedAt; | ||||||
|  | @override final  String feedId; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebArticle | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$WebArticleCopyWith<_WebArticle> get copyWith => __$WebArticleCopyWithImpl<_WebArticle>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$WebArticleToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(_meta),preview,content,publishedAt,feedId,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'WebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$WebArticleCopyWith<$Res> implements $WebArticleCopyWith<$Res> { | ||||||
|  |   factory _$WebArticleCopyWith(_WebArticle value, $Res Function(_WebArticle) _then) = __$WebArticleCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override $SnScrappedLinkCopyWith<$Res>? get preview; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$WebArticleCopyWithImpl<$Res> | ||||||
|  |     implements _$WebArticleCopyWith<$Res> { | ||||||
|  |   __$WebArticleCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _WebArticle _self; | ||||||
|  |   final $Res Function(_WebArticle) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of WebArticle | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_WebArticle( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable | ||||||
|  | as SnScrappedLink?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Create a copy of WebArticle | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnScrappedLinkCopyWith<$Res>? get preview { | ||||||
|  |     if (_self.preview == null) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) { | ||||||
|  |     return _then(_self.copyWith(preview: value)); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // dart format on | ||||||
							
								
								
									
										94
									
								
								lib/models/webfeed.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								lib/models/webfeed.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'webfeed.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _WebFeedConfig _$WebFeedConfigFromJson(Map<String, dynamic> json) => | ||||||
|  |     _WebFeedConfig(scrapPage: json['scrap_page'] as bool? ?? false); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$WebFeedConfigToJson(_WebFeedConfig instance) => | ||||||
|  |     <String, dynamic>{'scrap_page': instance.scrapPage}; | ||||||
|  |  | ||||||
|  | _WebFeed _$WebFeedFromJson(Map<String, dynamic> json) => _WebFeed( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   url: json['url'] as String, | ||||||
|  |   title: json['title'] as String, | ||||||
|  |   description: json['description'] as String?, | ||||||
|  |   preview: | ||||||
|  |       json['preview'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnScrappedLink.fromJson(json['preview'] as Map<String, dynamic>), | ||||||
|  |   config: | ||||||
|  |       json['config'] == null | ||||||
|  |           ? const WebFeedConfig() | ||||||
|  |           : WebFeedConfig.fromJson(json['config'] as Map<String, dynamic>), | ||||||
|  |   publisherId: json['publisher_id'] as String, | ||||||
|  |   articles: | ||||||
|  |       (json['articles'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => WebArticle.fromJson(e as Map<String, dynamic>)) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|  |   createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |   deletedAt: | ||||||
|  |       json['deleted_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['deleted_at'] as String), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$WebFeedToJson(_WebFeed instance) => <String, dynamic>{ | ||||||
|  |   'id': instance.id, | ||||||
|  |   'url': instance.url, | ||||||
|  |   'title': instance.title, | ||||||
|  |   'description': instance.description, | ||||||
|  |   'preview': instance.preview?.toJson(), | ||||||
|  |   'config': instance.config.toJson(), | ||||||
|  |   'publisher_id': instance.publisherId, | ||||||
|  |   'articles': instance.articles.map((e) => e.toJson()).toList(), | ||||||
|  |   'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |   'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |   'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | _WebArticle _$WebArticleFromJson(Map<String, dynamic> json) => _WebArticle( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   title: json['title'] as String, | ||||||
|  |   url: json['url'] as String, | ||||||
|  |   author: json['author'] as String?, | ||||||
|  |   meta: json['meta'] as Map<String, dynamic>?, | ||||||
|  |   preview: | ||||||
|  |       json['preview'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnScrappedLink.fromJson(json['preview'] as Map<String, dynamic>), | ||||||
|  |   content: json['content'] as String?, | ||||||
|  |   publishedAt: | ||||||
|  |       json['published_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['published_at'] as String), | ||||||
|  |   feedId: json['feed_id'] as String, | ||||||
|  |   createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |   deletedAt: | ||||||
|  |       json['deleted_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['deleted_at'] as String), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$WebArticleToJson(_WebArticle instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'title': instance.title, | ||||||
|  |       'url': instance.url, | ||||||
|  |       'author': instance.author, | ||||||
|  |       'meta': instance.meta, | ||||||
|  |       'preview': instance.preview?.toJson(), | ||||||
|  |       'content': instance.content, | ||||||
|  |       'published_at': instance.publishedAt?.toIso8601String(), | ||||||
|  |       'feed_id': instance.feedId, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |     }; | ||||||
| @@ -11,11 +11,6 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|  |  | ||||||
|   UserInfoNotifier(this._ref) : super(const AsyncValue.data(null)); |   UserInfoNotifier(this._ref) : super(const AsyncValue.data(null)); | ||||||
|  |  | ||||||
|   Future<String?> getAccessToken() async { |  | ||||||
|     final prefs = _ref.read(sharedPreferencesProvider); |  | ||||||
|     return prefs.getString(kTokenPairStoreKey); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<void> fetchUser() async { |   Future<void> fetchUser() async { | ||||||
|     try { |     try { | ||||||
|       final client = _ref.read(apiClientProvider); |       final client = _ref.read(apiClientProvider); | ||||||
|   | |||||||
							
								
								
									
										114
									
								
								lib/pods/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								lib/pods/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  |  | ||||||
|  | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
|  | import 'package:island/models/webfeed.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  |  | ||||||
|  | final webFeedListProvider = FutureProvider.family<List<WebFeed>, String>(( | ||||||
|  |   ref, | ||||||
|  |   pubName, | ||||||
|  | ) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final response = await client.get('/publishers/$pubName/feeds'); | ||||||
|  |   return (response.data as List).map((json) => WebFeed.fromJson(json)).toList(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class WebFeedNotifier | ||||||
|  |     extends | ||||||
|  |         AutoDisposeFamilyAsyncNotifier< | ||||||
|  |           WebFeed, | ||||||
|  |           ({String pubName, String? feedId}) | ||||||
|  |         > { | ||||||
|  |   @override | ||||||
|  |   FutureOr<WebFeed> build(({String pubName, String? feedId}) arg) async { | ||||||
|  |     if (arg.feedId == null || arg.feedId!.isEmpty) { | ||||||
|  |       return WebFeed( | ||||||
|  |         id: '', | ||||||
|  |         url: '', | ||||||
|  |         title: '', | ||||||
|  |         publisherId: arg.pubName, | ||||||
|  |         createdAt: DateTime.now(), | ||||||
|  |         updatedAt: DateTime.now(), | ||||||
|  |         deletedAt: null, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/publishers/${arg.pubName}/feeds/${arg.feedId}', | ||||||
|  |       ); | ||||||
|  |       return WebFeed.fromJson(response.data); | ||||||
|  |     } catch (e) { | ||||||
|  |       rethrow; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> saveFeed(WebFeed feed) async { | ||||||
|  |     state = const AsyncValue.loading(); | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final url = '/publishers/${feed.publisherId}/feeds'; | ||||||
|  |  | ||||||
|  |       final response = | ||||||
|  |           feed.id.isEmpty | ||||||
|  |               ? await client.post(url, data: feed.toJson()) | ||||||
|  |               : await client.patch('$url/${feed.id}', data: feed.toJson()); | ||||||
|  |  | ||||||
|  |       state = AsyncValue.data(WebFeed.fromJson(response.data)); | ||||||
|  |     } catch (error, stackTrace) { | ||||||
|  |       state = AsyncValue.error(error, stackTrace); | ||||||
|  |       rethrow; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> deleteFeed() async { | ||||||
|  |     final feedId = arg.feedId; | ||||||
|  |     if (feedId == null || feedId.isEmpty) return; | ||||||
|  |  | ||||||
|  |     state = const AsyncValue.loading(); | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       await client.delete('/publishers/${arg.pubName}/feeds/$feedId'); | ||||||
|  |       state = AsyncValue.data( | ||||||
|  |         WebFeed( | ||||||
|  |           id: '', | ||||||
|  |           url: '', | ||||||
|  |           title: '', | ||||||
|  |           publisherId: arg.pubName, | ||||||
|  |           createdAt: DateTime.now(), | ||||||
|  |           updatedAt: DateTime.now(), | ||||||
|  |           deletedAt: null, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } catch (error, stackTrace) { | ||||||
|  |       state = AsyncValue.error(error, stackTrace); | ||||||
|  |       rethrow; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> scrapFeed() async { | ||||||
|  |     final feedId = arg.feedId; | ||||||
|  |     if (feedId == null || feedId.isEmpty) return; | ||||||
|  |  | ||||||
|  |     state = const AsyncValue.loading(); | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       await client.post('/publishers/${arg.pubName}/feeds/$feedId/scrap'); | ||||||
|  |  | ||||||
|  |       // Reload the feed | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/publishers/${arg.pubName}/feeds/$feedId', | ||||||
|  |       ); | ||||||
|  |       state = AsyncValue.data(WebFeed.fromJson(response.data)); | ||||||
|  |     } catch (error, stackTrace) { | ||||||
|  |       state = AsyncValue.error(error, stackTrace); | ||||||
|  |       rethrow; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | final webFeedNotifierProvider = AsyncNotifierProvider.autoDispose | ||||||
|  |     .family<WebFeedNotifier, WebFeed, ({String pubName, String? feedId})>( | ||||||
|  |       WebFeedNotifier.new, | ||||||
|  |     ); | ||||||
							
								
								
									
										158
									
								
								lib/route.dart
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								lib/route.dart
									
									
									
									
									
								
							| @@ -1,6 +1,10 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.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/screens/developers/apps.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_app.dart'; | ||||||
|  | import 'package:island/screens/developers/new_app.dart'; | ||||||
|  | import 'package:island/screens/developers/hub.dart'; | ||||||
| import 'package:island/widgets/app_wrapper.dart'; | import 'package:island/widgets/app_wrapper.dart'; | ||||||
| import 'package:island/screens/tabs.dart'; | import 'package:island/screens/tabs.dart'; | ||||||
|  |  | ||||||
| @@ -18,18 +22,20 @@ import 'package:island/screens/chat/room.dart'; | |||||||
| import 'package:island/screens/chat/room_detail.dart'; | import 'package:island/screens/chat/room_detail.dart'; | ||||||
| import 'package:island/screens/chat/call.dart'; | import 'package:island/screens/chat/call.dart'; | ||||||
| import 'package:island/screens/creators/hub.dart'; | import 'package:island/screens/creators/hub.dart'; | ||||||
| import 'package:island/screens/creators/posts/list.dart'; | import 'package:island/screens/creators/posts/post_manage_list.dart'; | ||||||
| import 'package:island/screens/creators/stickers/stickers.dart'; | import 'package:island/screens/creators/stickers/stickers.dart'; | ||||||
| import 'package:island/screens/creators/stickers/pack_detail.dart'; | import 'package:island/screens/creators/stickers/pack_detail.dart'; | ||||||
| import 'package:island/screens/creators/publishers.dart'; | import 'package:island/screens/creators/publishers.dart'; | ||||||
|  | import 'package:island/screens/creators/webfeed/webfeed_list.dart'; | ||||||
|  | import 'package:island/screens/creators/webfeed/webfeed_edit.dart'; | ||||||
| import 'package:island/screens/posts/compose.dart'; | import 'package:island/screens/posts/compose.dart'; | ||||||
| import 'package:island/screens/posts/detail.dart'; | import 'package:island/screens/posts/post_detail.dart'; | ||||||
| import 'package:island/screens/posts/pub_profile.dart'; | import 'package:island/screens/posts/pub_profile.dart'; | ||||||
| import 'package:island/screens/auth/login.dart'; | import 'package:island/screens/auth/login.dart'; | ||||||
| import 'package:island/screens/auth/create_account.dart'; | import 'package:island/screens/auth/create_account.dart'; | ||||||
| import 'package:island/screens/settings.dart'; | 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/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'; | import 'package:island/screens/discovery/realms.dart'; | ||||||
|  |  | ||||||
| @@ -79,33 +85,64 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|               return EventCalanderScreen(name: name); |               return EventCalanderScreen(name: name); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|           GoRoute( |           ShellRoute( | ||||||
|             path: '/creators', |             builder: | ||||||
|             builder: (context, state) => const CreatorHubScreen(), |                 (context, state, child) => CreatorHubShellScreen(child: child), | ||||||
|             routes: [ |             routes: [ | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/posts', |                 path: '/creators', | ||||||
|  |                 builder: (context, state) => const CreatorHubScreen(), | ||||||
|  |               ), | ||||||
|  |               // Web Feed Routes | ||||||
|  |               GoRoute( | ||||||
|  |                 path: '/creators/:name/feeds', | ||||||
|  |                 builder: (context, state) { | ||||||
|  |                   final name = state.pathParameters['name']!; | ||||||
|  |                   return WebFeedListScreen(pubName: name); | ||||||
|  |                 }, | ||||||
|  |                 routes: [ | ||||||
|  |                   GoRoute( | ||||||
|  |                     path: 'new', | ||||||
|  |                     builder: (context, state) { | ||||||
|  |                       return WebFeedNewScreen( | ||||||
|  |                         pubName: state.pathParameters['name']!, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     path: ':feedId', | ||||||
|  |                     builder: (context, state) { | ||||||
|  |                       return WebFeedEditScreen( | ||||||
|  |                         pubName: state.pathParameters['name']!, | ||||||
|  |                         feedId: state.pathParameters['feedId'], | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 path: '/creators/:name/posts', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) { | ||||||
|                   final name = state.pathParameters['name']!; |                   final name = state.pathParameters['name']!; | ||||||
|                   return CreatorPostListScreen(pubName: name); |                   return CreatorPostListScreen(pubName: name); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/stickers', |                 path: '/creators/:name/stickers', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) { | ||||||
|                   final name = state.pathParameters['name']!; |                   final name = state.pathParameters['name']!; | ||||||
|                   return StickersScreen(pubName: name); |                   return StickersScreen(pubName: name); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/stickers/new', |                 path: '/creators/:name/stickers/new', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) { | ||||||
|                   final name = state.pathParameters['name']!; |                   final name = state.pathParameters['name']!; | ||||||
|                   return NewStickerPacksScreen(pubName: name); |                   return NewStickerPacksScreen(pubName: name); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/stickers/:packId/edit', |                 path: '/creators/:name/stickers/:packId/edit', | ||||||
|                 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']!; | ||||||
| @@ -113,7 +150,7 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/stickers/:packId', |                 path: '/creators/:name/stickers/:packId', | ||||||
|                 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']!; | ||||||
| @@ -121,14 +158,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/stickers/:packId/new', |                 path: '/creators/:name/stickers/:packId/new', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) { | ||||||
|                   final packId = state.pathParameters['packId']!; |                   final packId = state.pathParameters['packId']!; | ||||||
|                   return NewStickersScreen(packId: packId); |                   return NewStickersScreen(packId: packId); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/stickers/:packId/:id/edit', |                 path: '/creators/:name/stickers/:packId/:id/edit', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) { | ||||||
|                   final packId = state.pathParameters['packId']!; |                   final packId = state.pathParameters['packId']!; | ||||||
|                   final id = state.pathParameters['id']!; |                   final id = state.pathParameters['id']!; | ||||||
| @@ -136,11 +173,11 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: 'new', |                 path: '/creators/new', | ||||||
|                 builder: (context, state) => const NewPublisherScreen(), |                 builder: (context, state) => const NewPublisherScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 path: ':name/edit', |                 path: '/creators/:name/edit', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) { | ||||||
|                   final name = state.pathParameters['name']!; |                   final name = state.pathParameters['name']!; | ||||||
|                   return EditPublisherScreen(name: name); |                   return EditPublisherScreen(name: name); | ||||||
| @@ -148,6 +185,39 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|               ), |               ), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|  |           ShellRoute( | ||||||
|  |             builder: | ||||||
|  |                 (context, state, child) => | ||||||
|  |                     DeveloperHubShellScreen(child: child), | ||||||
|  |             routes: [ | ||||||
|  |               GoRoute( | ||||||
|  |                 path: '/developers', | ||||||
|  |                 builder: (context, state) => const DeveloperHubScreen(), | ||||||
|  |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 path: '/developers/:name/apps', | ||||||
|  |                 builder: | ||||||
|  |                     (context, state) => CustomAppsScreen( | ||||||
|  |                       publisherName: state.pathParameters['name']!, | ||||||
|  |                     ), | ||||||
|  |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 path: '/developers/:name/apps/new', | ||||||
|  |                 builder: | ||||||
|  |                     (context, state) => NewCustomAppScreen( | ||||||
|  |                       publisherName: state.pathParameters['name']!, | ||||||
|  |                     ), | ||||||
|  |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 path: '/developers/:name/apps/:id', | ||||||
|  |                 builder: | ||||||
|  |                     (context, state) => EditAppScreen( | ||||||
|  |                       publisherName: state.pathParameters['name']!, | ||||||
|  |                       id: state.pathParameters['id']!, | ||||||
|  |                     ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |  | ||||||
|           // Auth routes |           // Auth routes | ||||||
|           GoRoute( |           GoRoute( | ||||||
| @@ -173,56 +243,64 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|             }, |             }, | ||||||
|             routes: [ |             routes: [ | ||||||
|               // Explore tab |               // Explore tab | ||||||
|               GoRoute( |               ShellRoute( | ||||||
|                 path: '/', |                 builder: | ||||||
|                 builder: (context, state) => const ExploreScreen(), |                     (context, state, child) => ExploreShellScreen(child: child), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'posts/:id', |                     path: '/', | ||||||
|  |                     builder: (context, state) => const ExploreScreen(), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     path: '/posts/:id', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
|                       final id = state.pathParameters['id']!; |                       final id = state.pathParameters['id']!; | ||||||
|                       return PostDetailScreen(id: id); |                       return PostDetailScreen(id: id); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'publishers/:name', |                     path: '/publishers/:name', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
|                       final name = state.pathParameters['name']!; |                       final name = state.pathParameters['name']!; | ||||||
|                       return PublisherProfileScreen(name: name); |                       return PublisherProfileScreen(name: name); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'discovery/realms', |                     path: '/discovery/realms', | ||||||
|                     builder: (context, state) => const DiscoveryRealmsScreen(), |                     builder: (context, state) => const DiscoveryRealmsScreen(), | ||||||
|                   ), |                   ), | ||||||
|                 ], |                 ], | ||||||
|               ), |               ), | ||||||
|  |  | ||||||
|               // Chat tab |               // Chat tab | ||||||
|               GoRoute( |               ShellRoute( | ||||||
|                 path: '/chat', |                 builder: | ||||||
|                 builder: (context, state) => const ChatListScreen(), |                     (context, state, child) => ChatShellScreen(child: child), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'new', |                     path: '/chat', | ||||||
|  |                     builder: (context, state) => const ChatListScreen(), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     path: '/chat/new', | ||||||
|                     builder: (context, state) => const NewChatScreen(), |                     builder: (context, state) => const NewChatScreen(), | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: ':id', |                     path: '/chat/:id', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
|                       final id = state.pathParameters['id']!; |                       final id = state.pathParameters['id']!; | ||||||
|                       return ChatRoomScreen(id: id); |                       return ChatRoomScreen(id: id); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: ':id/edit', |                     path: '/chat/:id/edit', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
|                       final id = state.pathParameters['id']!; |                       final id = state.pathParameters['id']!; | ||||||
|                       return EditChatScreen(id: id); |                       return EditChatScreen(id: id); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: ':id/detail', |                     path: '/chat/:id/detail', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
|                       final id = state.pathParameters['id']!; |                       final id = state.pathParameters['id']!; | ||||||
|                       return ChatDetailScreen(id: id); |                       return ChatDetailScreen(id: id); | ||||||
| @@ -258,39 +336,43 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|               ), |               ), | ||||||
|  |  | ||||||
|               // Account tab |               // Account tab | ||||||
|               GoRoute( |               ShellRoute( | ||||||
|                 path: '/account', |                 builder: | ||||||
|                 builder: (context, state) => const AccountScreen(), |                     (context, state, child) => AccountShellScreen(child: child), | ||||||
|                 routes: [ |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'notifications', |                     path: '/account', | ||||||
|  |                     builder: (context, state) => const AccountScreen(), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     path: '/account/notifications', | ||||||
|                     builder: (context, state) => const NotificationScreen(), |                     builder: (context, state) => const NotificationScreen(), | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'wallet', |                     path: '/account/wallet', | ||||||
|                     builder: (context, state) => const WalletScreen(), |                     builder: (context, state) => const WalletScreen(), | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'relationships', |                     path: '/account/relationships', | ||||||
|                     builder: (context, state) => const RelationshipScreen(), |                     builder: (context, state) => const RelationshipScreen(), | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: ':name', |                     path: '/account/:name', | ||||||
|                     builder: (context, state) { |                     builder: (context, state) { | ||||||
|                       final name = state.pathParameters['name']!; |                       final name = state.pathParameters['name']!; | ||||||
|                       return AccountProfileScreen(name: name); |                       return AccountProfileScreen(name: name); | ||||||
|                     }, |                     }, | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'me/update', |                     path: '/account/me/update', | ||||||
|                     builder: (context, state) => const UpdateProfileScreen(), |                     builder: (context, state) => const UpdateProfileScreen(), | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'me/leveling', |                     path: '/account/me/leveling', | ||||||
|                     builder: (context, state) => const LevelingScreen(), |                     builder: (context, state) => const LevelingScreen(), | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     path: 'settings', |                     path: '/account/settings', | ||||||
|                     builder: (context, state) => const AccountSettingsScreen(), |                     builder: (context, state) => const AccountSettingsScreen(), | ||||||
|                   ), |                   ), | ||||||
|                 ], |                 ], | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 progress: user.value!.profile.levelingProgress, |                 progress: user.value!.profile.levelingProgress, | ||||||
|               ), |               ), | ||||||
|               onTap: () { |               onTap: () { | ||||||
|                 context.push('/account/leveling'); |                 context.push('/account/me/leveling'); | ||||||
|               }, |               }, | ||||||
|             ).padding(horizontal: 12), |             ).padding(horizontal: 12), | ||||||
|             Row( |             Row( | ||||||
| @@ -178,7 +178,9 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                           Text('developerPortalDescription').tr(), |                           Text('developerPortalDescription').tr(), | ||||||
|                         ], |                         ], | ||||||
|                       ).padding(horizontal: 16, vertical: 12), |                       ).padding(horizontal: 16, vertical: 12), | ||||||
|                       onTap: () {}, |                       onTap: () { | ||||||
|  |                         context.push('/developers'); | ||||||
|  |                       }, | ||||||
|                     ), |                     ), | ||||||
|                   ).height(140), |                   ).height(140), | ||||||
|                 ), |                 ), | ||||||
| @@ -210,7 +212,7 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|               title: Text('wallet').tr(), |               title: Text('wallet').tr(), | ||||||
|               onTap: () { |               onTap: () { | ||||||
|                 context.push('/wallet'); |                 context.push('/account/wallet'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|   | |||||||
| @@ -341,7 +341,10 @@ class UpdateProfileScreen extends HookConsumerWidget { | |||||||
|                   ), |                   ), | ||||||
|  |  | ||||||
|                   TextFormField( |                   TextFormField( | ||||||
|                     decoration: InputDecoration(labelText: 'bio'.tr()), |                     decoration: InputDecoration( | ||||||
|  |                       labelText: 'bio'.tr(), | ||||||
|  |                       alignLabelWithHint: true, | ||||||
|  |                     ), | ||||||
|                     maxLines: null, |                     maxLines: null, | ||||||
|                     minLines: 3, |                     minLines: 3, | ||||||
|                     controller: bioController, |                     controller: bioController, | ||||||
|   | |||||||
| @@ -53,17 +53,21 @@ Future<List<SnAccountBadge>> accountBadges(Ref ref, String uname) async { | |||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async { | Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async { | ||||||
|   final account = await ref.watch(accountProvider(uname).future); |   try { | ||||||
|   if (account.profile.background == null) return null; |     final account = await ref.watch(accountProvider(uname).future); | ||||||
|   final palette = await PaletteGenerator.fromImageProvider( |     if (account.profile.background == null) return null; | ||||||
|     CloudImageWidget.provider( |     final palette = await PaletteGenerator.fromImageProvider( | ||||||
|       fileId: account.profile.background!.id, |       CloudImageWidget.provider( | ||||||
|       serverUrl: ref.watch(serverUrlProvider), |         fileId: account.profile.background!.id, | ||||||
|     ), |         serverUrl: ref.watch(serverUrlProvider), | ||||||
|   ); |       ), | ||||||
|   final dominantColor = palette.dominantColor?.color; |     ); | ||||||
|   if (dominantColor == null) return null; |     final dominantColor = palette.dominantColor?.color; | ||||||
|   return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; |     if (dominantColor == null) return null; | ||||||
|  |     return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; | ||||||
|  |   } catch (_) { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
|   | |||||||
| @@ -268,7 +268,7 @@ class _AccountBadgesProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$accountAppbarForcegroundColorHash() => | String _$accountAppbarForcegroundColorHash() => | ||||||
|     r'f654a7a5594eda1500906e9ad023c22772257a9b'; |     r'8ee0cae10817b77fb09548a482f5247662b4374c'; | ||||||
|  |  | ||||||
| /// See also [accountAppbarForcegroundColor]. | /// See also [accountAppbarForcegroundColor]. | ||||||
| @ProviderFor(accountAppbarForcegroundColor) | @ProviderFor(accountAppbarForcegroundColor) | ||||||
|   | |||||||
| @@ -669,24 +669,46 @@ class EditChatScreen extends HookConsumerWidget { | |||||||
|                 const SizedBox(height: 16), |                 const SizedBox(height: 16), | ||||||
|                 TextFormField( |                 TextFormField( | ||||||
|                   controller: descriptionController, |                   controller: descriptionController, | ||||||
|                   decoration: const InputDecoration(labelText: 'Description'), |                   decoration: const InputDecoration( | ||||||
|  |                     labelText: 'Description', | ||||||
|  |                     alignLabelWithHint: true, | ||||||
|  |                   ), | ||||||
|                   minLines: 3, |                   minLines: 3, | ||||||
|                   maxLines: null, |                   maxLines: null, | ||||||
|                   onTapOutside: |                   onTapOutside: | ||||||
|                       (_) => FocusManager.instance.primaryFocus?.unfocus(), |                       (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                 ), |                 ), | ||||||
|                 const SizedBox(height: 16), |                 const SizedBox(height: 16), | ||||||
|                 CheckboxListTile( |                 Card( | ||||||
|                   title: const Text('isPublic').tr(), |                   margin: EdgeInsets.zero, | ||||||
|                   subtitle: const Text('isPublicHint').tr(), |                   child: Column( | ||||||
|                   value: isPublic.value, |                     children: [ | ||||||
|                   onChanged: (value) => isPublic.value = value ?? false, |                       CheckboxListTile( | ||||||
|                 ), |                         secondary: const Icon(Symbols.public), | ||||||
|                 CheckboxListTile( |                         title: Text('publicChat').tr(), | ||||||
|                   title: const Text('isCommunity').tr(), |                         subtitle: Text('publicChatDescription').tr(), | ||||||
|                   subtitle: const Text('isCommunityHint').tr(), |                         value: isPublic.value, | ||||||
|                   value: isCommunity.value, |                         onChanged: (value) { | ||||||
|                   onChanged: (value) => isCommunity.value = value ?? false, |                           isPublic.value = value ?? true; | ||||||
|  |                         }, | ||||||
|  |                         shape: RoundedRectangleBorder( | ||||||
|  |                           borderRadius: BorderRadius.circular(8), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                       CheckboxListTile( | ||||||
|  |                         secondary: const Icon(Symbols.travel_explore), | ||||||
|  |                         title: Text('communityChat').tr(), | ||||||
|  |                         subtitle: Text('communityChatDescription').tr(), | ||||||
|  |                         value: isCommunity.value, | ||||||
|  |                         onChanged: (value) { | ||||||
|  |                           isCommunity.value = value ?? false; | ||||||
|  |                         }, | ||||||
|  |                         shape: RoundedRectangleBorder( | ||||||
|  |                           borderRadius: BorderRadius.circular(8), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|                 ), |                 ), | ||||||
|                 const SizedBox(height: 16), |                 const SizedBox(height: 16), | ||||||
|                 Align( |                 Align( | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:dio/dio.dart'; | ||||||
| import 'package:dropdown_button2/dropdown_button2.dart'; | import 'package:dropdown_button2/dropdown_button2.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| @@ -6,14 +7,20 @@ 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/post.dart'; | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/models/publisher.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/creators/publishers.dart'; | import 'package:island/screens/creators/publishers.dart'; | ||||||
| import 'package:island/services/responsive.dart'; | import 'package:island/services/responsive.dart'; | ||||||
|  | import 'package:island/services/text.dart'; | ||||||
|  | import 'package:island/widgets/account/account_picker.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | 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:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| part 'hub.g.dart'; | part 'hub.g.dart'; | ||||||
| @@ -26,6 +33,73 @@ Future<SnPublisherStats?> publisherStats(Ref ref, String? uname) async { | |||||||
|   return SnPublisherStats.fromJson(resp.data); |   return SnPublisherStats.fromJson(resp.data); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnPublisherMember?> publisherIdentity(Ref ref, String uname) async { | ||||||
|  |   try { | ||||||
|  |     final apiClient = ref.watch(apiClientProvider); | ||||||
|  |     final response = await apiClient.get('/publishers/$uname/members/me'); | ||||||
|  |     return SnPublisherMember.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<Map<String, bool>> publisherFeatures(Ref ref, String? uname) async { | ||||||
|  |   if (uname == null) return {}; | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   final response = await apiClient.get('/publishers/$uname/features'); | ||||||
|  |   return Map<String, bool>.from(response.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<SnPublisherMember>> publisherInvites(Ref ref) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/publishers/invites'); | ||||||
|  |   return resp.data | ||||||
|  |       .map((e) => SnPublisherMember.fromJson(e)) | ||||||
|  |       .cast<SnPublisherMember>() | ||||||
|  |       .toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class PublisherMemberListNotifier extends _$PublisherMemberListNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnPublisherMember> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnPublisherMember>> build(String uname) async { | ||||||
|  |     return fetch(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnPublisherMember>> fetch({String? cursor}) async { | ||||||
|  |     final apiClient = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor != null ? int.parse(cursor) : 0; | ||||||
|  |  | ||||||
|  |     final response = await apiClient.get( | ||||||
|  |       '/publishers/$uname/members', | ||||||
|  |       queryParameters: {'offset': offset, 'take': _pageSize}, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final members = data.map((e) => SnPublisherMember.fromJson(e)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + members.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + members.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: members, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class CreatorHubShellScreen extends StatelessWidget { | class CreatorHubShellScreen extends StatelessWidget { | ||||||
|   final Widget child; |   final Widget child; | ||||||
|   const CreatorHubShellScreen({super.key, required this.child}); |   const CreatorHubShellScreen({super.key, required this.child}); | ||||||
| @@ -58,21 +132,20 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     final publishers = ref.watch(publishersManagedProvider); |     final publishers = ref.watch(publishersManagedProvider); | ||||||
|  |     final publisherInvites = ref.watch(publisherInvitesProvider); | ||||||
|     final currentPublisher = useState<SnPublisher?>( |     final currentPublisher = useState<SnPublisher?>( | ||||||
|       publishers.value?.firstOrNull, |       publishers.value?.firstOrNull, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     void updatePublisher() { |     void updatePublisher() { | ||||||
|       context |       context.push('/creators/${currentPublisher.value!.name}/edit').then(( | ||||||
|           .push('/creators/${currentPublisher.value!.name}/edit') |         value, | ||||||
|           .then((value) async { |       ) async { | ||||||
|             if (value == null) return; |         if (value == null) return; | ||||||
|             final data = await ref.refresh(publishersManagedProvider.future); |         final data = await ref.refresh(publishersManagedProvider.future); | ||||||
|             currentPublisher.value = |         currentPublisher.value = | ||||||
|                 data |             data.where((e) => e.id == currentPublisher.value!.id).firstOrNull; | ||||||
|                     .where((e) => e.id == currentPublisher.value!.id) |       }); | ||||||
|                     .firstOrNull; |  | ||||||
|           }); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void deletePublisher() { |     void deletePublisher() { | ||||||
| @@ -120,12 +193,40 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|       publisherStatsProvider(currentPublisher.value?.name), |       publisherStatsProvider(currentPublisher.value?.name), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     final publisherFeatures = ref.watch( | ||||||
|  |       publisherFeaturesProvider(currentPublisher.value?.name), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       noBackground: false, |       noBackground: false, | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         leading: !isWide ? const PageBackButton() : null, |         leading: !isWide ? const PageBackButton() : null, | ||||||
|         title: Text('creatorHub').tr(), |         title: Text('creatorHub').tr(), | ||||||
|         actions: [ |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: Badge( | ||||||
|  |               label: Text( | ||||||
|  |                 publisherInvites.when( | ||||||
|  |                   data: (invites) => invites.length.toString(), | ||||||
|  |                   error: (_, _) => '0', | ||||||
|  |                   loading: () => '0', | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               isLabelVisible: publisherInvites.when( | ||||||
|  |                 data: (invites) => invites.isNotEmpty, | ||||||
|  |                 error: (_, _) => false, | ||||||
|  |                 loading: () => false, | ||||||
|  |               ), | ||||||
|  |               child: const Icon(Symbols.email), | ||||||
|  |             ), | ||||||
|  |             onPressed: () { | ||||||
|  |               showModalBottomSheet( | ||||||
|  |                 context: context, | ||||||
|  |                 isScrollControlled: true, | ||||||
|  |                 builder: (_) => const _PublisherInviteSheet(), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|           DropdownButtonHideUnderline( |           DropdownButtonHideUnderline( | ||||||
|             child: DropdownButton2<SnPublisher>( |             child: DropdownButton2<SnPublisher>( | ||||||
|               alignment: Alignment.centerRight, |               alignment: Alignment.centerRight, | ||||||
| @@ -203,7 +304,7 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|                           ...(publishers.value?.map( |                           ...(publishers.value?.map( | ||||||
|                                 (publisher) => ListTile( |                                 (publisher) => ListTile( | ||||||
|                                   leading: ProfilePictureWidget( |                                   leading: ProfilePictureWidget( | ||||||
|                                     fileId: publisher.picture?.id, |                                     file: publisher.picture, | ||||||
|                                   ), |                                   ), | ||||||
|                                   title: Text(publisher.nick), |                                   title: Text(publisher.nick), | ||||||
|                                   subtitle: Text('@${publisher.name}'), |                                   subtitle: Text('@${publisher.name}'), | ||||||
| @@ -266,6 +367,86 @@ class CreatorHubScreen extends HookConsumerWidget { | |||||||
|                               ); |                               ); | ||||||
|                             }, |                             }, | ||||||
|                           ), |                           ), | ||||||
|  |                           ListTile( | ||||||
|  |                             minTileHeight: 48, | ||||||
|  |                             title: Text('publisherMembers').tr(), | ||||||
|  |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                             leading: const Icon(Symbols.group), | ||||||
|  |                             contentPadding: const EdgeInsets.symmetric( | ||||||
|  |                               horizontal: 24, | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               showModalBottomSheet( | ||||||
|  |                                 isScrollControlled: true, | ||||||
|  |                                 context: context, | ||||||
|  |                                 builder: | ||||||
|  |                                     (context) => _PublisherMemberListSheet( | ||||||
|  |                                       publisherUname: | ||||||
|  |                                           currentPublisher.value!.name, | ||||||
|  |                                     ), | ||||||
|  |                               ); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           ListTile( | ||||||
|  |                             minTileHeight: 48, | ||||||
|  |                             title: const Text('Web Feeds').tr(), | ||||||
|  |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                             leading: const Icon(Symbols.rss_feed), | ||||||
|  |                             contentPadding: const EdgeInsets.symmetric( | ||||||
|  |                               horizontal: 24, | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.push( | ||||||
|  |                                 '/creators/${currentPublisher.value!.name}/feeds', | ||||||
|  |                               ); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           ExpansionTile( | ||||||
|  |                             title: Text('publisherFeatures').tr(), | ||||||
|  |                             leading: const Icon(Symbols.flag), | ||||||
|  |                             tilePadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                             minTileHeight: 48, | ||||||
|  |                             children: [ | ||||||
|  |                               ...publisherFeatures.when( | ||||||
|  |                                 data: (data) { | ||||||
|  |                                   return data.entries.map((entry) { | ||||||
|  |                                     final keyPrefix = | ||||||
|  |                                         'publisherFeature${entry.key.capitalizeEachWord()}'; | ||||||
|  |                                     return ListTile( | ||||||
|  |                                       minTileHeight: 48, | ||||||
|  |                                       contentPadding: EdgeInsets.symmetric( | ||||||
|  |                                         horizontal: 24, | ||||||
|  |                                       ), | ||||||
|  |                                       leading: Icon( | ||||||
|  |                                         Symbols.circle, | ||||||
|  |                                         color: | ||||||
|  |                                             entry.value | ||||||
|  |                                                 ? Colors.green | ||||||
|  |                                                 : Colors.red, | ||||||
|  |                                         fill: 1, | ||||||
|  |                                         size: 16, | ||||||
|  |                                       ).padding(left: 2, top: 4), | ||||||
|  |                                       title: Text(keyPrefix).tr(), | ||||||
|  |                                       subtitle: Column( | ||||||
|  |                                         crossAxisAlignment: | ||||||
|  |                                             CrossAxisAlignment.start, | ||||||
|  |                                         children: [ | ||||||
|  |                                           Text('${keyPrefix}Description').tr(), | ||||||
|  |                                           if (!entry.value) | ||||||
|  |                                             Text( | ||||||
|  |                                               '${keyPrefix}Hint', | ||||||
|  |                                             ).tr().bold(), | ||||||
|  |                                         ], | ||||||
|  |                                       ), | ||||||
|  |                                       isThreeLine: true, | ||||||
|  |                                     ); | ||||||
|  |                                   }).toList(); | ||||||
|  |                                 }, | ||||||
|  |                                 error: (_, _) => [], | ||||||
|  |                                 loading: () => [], | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|                           Divider(height: 1).padding(vertical: 8), |                           Divider(height: 1).padding(vertical: 8), | ||||||
|                           ListTile( |                           ListTile( | ||||||
|                             minTileHeight: 48, |                             minTileHeight: 48, | ||||||
| @@ -393,3 +574,482 @@ class _PublisherStatsWidget extends StatelessWidget { | |||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class PublisherMemberState { | ||||||
|  |   final List<SnPublisherMember> members; | ||||||
|  |   final bool isLoading; | ||||||
|  |   final int total; | ||||||
|  |   final String? error; | ||||||
|  |  | ||||||
|  |   const PublisherMemberState({ | ||||||
|  |     required this.members, | ||||||
|  |     required this.isLoading, | ||||||
|  |     required this.total, | ||||||
|  |     this.error, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   PublisherMemberState copyWith({ | ||||||
|  |     List<SnPublisherMember>? members, | ||||||
|  |     bool? isLoading, | ||||||
|  |     int? total, | ||||||
|  |     String? error, | ||||||
|  |   }) { | ||||||
|  |     return PublisherMemberState( | ||||||
|  |       members: members ?? this.members, | ||||||
|  |       isLoading: isLoading ?? this.isLoading, | ||||||
|  |       total: total ?? this.total, | ||||||
|  |       error: error ?? this.error, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | final publisherMemberStateProvider = StateNotifierProvider.family< | ||||||
|  |   PublisherMemberNotifier, | ||||||
|  |   PublisherMemberState, | ||||||
|  |   String | ||||||
|  | >((ref, publisherUname) { | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   return PublisherMemberNotifier(apiClient, publisherUname); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class PublisherMemberNotifier extends StateNotifier<PublisherMemberState> { | ||||||
|  |   final String publisherUname; | ||||||
|  |   final Dio _apiClient; | ||||||
|  |  | ||||||
|  |   PublisherMemberNotifier(this._apiClient, this.publisherUname) | ||||||
|  |     : super( | ||||||
|  |         const PublisherMemberState(members: [], isLoading: false, total: 0), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   Future<void> loadMore({int offset = 0, int take = 20}) async { | ||||||
|  |     if (state.isLoading) return; | ||||||
|  |     if (state.total > 0 && state.members.length >= state.total) return; | ||||||
|  |  | ||||||
|  |     state = state.copyWith(isLoading: true, error: null); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final response = await _apiClient.get( | ||||||
|  |         '/publishers/$publisherUname/members', | ||||||
|  |         queryParameters: {'offset': offset, 'take': take}, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |       final List<dynamic> data = response.data; | ||||||
|  |       final members = data.map((e) => SnPublisherMember.fromJson(e)).toList(); | ||||||
|  |  | ||||||
|  |       state = state.copyWith( | ||||||
|  |         members: [...state.members, ...members], | ||||||
|  |         total: total, | ||||||
|  |         isLoading: false, | ||||||
|  |       ); | ||||||
|  |     } catch (e) { | ||||||
|  |       state = state.copyWith(error: e.toString(), isLoading: false); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void reset() { | ||||||
|  |     state = const PublisherMemberState(members: [], isLoading: false, total: 0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherMemberListSheet extends HookConsumerWidget { | ||||||
|  |   final String publisherUname; | ||||||
|  |   const _PublisherMemberListSheet({required this.publisherUname}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final publisherIdentity = ref.watch( | ||||||
|  |       publisherIdentityProvider(publisherUname), | ||||||
|  |     ); | ||||||
|  |     final memberListProvider = publisherMemberListNotifierProvider( | ||||||
|  |       publisherUname, | ||||||
|  |     ); | ||||||
|  |     final memberState = ref.watch(publisherMemberStateProvider(publisherUname)); | ||||||
|  |     final memberNotifier = ref.read( | ||||||
|  |       publisherMemberStateProvider(publisherUname).notifier, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       Future(() { | ||||||
|  |         memberNotifier.loadMore(); | ||||||
|  |       }); | ||||||
|  |       return null; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|  |     Future<void> invitePerson() async { | ||||||
|  |       final result = await showModalBottomSheet( | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         context: context, | ||||||
|  |         builder: (context) => const AccountPickerSheet(), | ||||||
|  |       ); | ||||||
|  |       if (result == null) return; | ||||||
|  |       try { | ||||||
|  |         final apiClient = ref.watch(apiClientProvider); | ||||||
|  |         await apiClient.post( | ||||||
|  |           '/publishers/$publisherUname/invites', | ||||||
|  |           data: {'related_user_id': result.id, 'role': 0}, | ||||||
|  |         ); | ||||||
|  |         ref.invalidate(memberListProvider); | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return Container( | ||||||
|  |       constraints: BoxConstraints( | ||||||
|  |         maxHeight: MediaQuery.of(context).size.height * 0.8, | ||||||
|  |       ), | ||||||
|  |       child: Column( | ||||||
|  |         children: [ | ||||||
|  |           Padding( | ||||||
|  |             padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), | ||||||
|  |             child: Row( | ||||||
|  |               children: [ | ||||||
|  |                 Text( | ||||||
|  |                   'members'.plural(memberState.total), | ||||||
|  |                   style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                     fontWeight: FontWeight.w600, | ||||||
|  |                     letterSpacing: -0.5, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 const Spacer(), | ||||||
|  |                 IconButton( | ||||||
|  |                   icon: const Icon(Symbols.person_add), | ||||||
|  |                   onPressed: invitePerson, | ||||||
|  |                   style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||||
|  |                 ), | ||||||
|  |                 IconButton( | ||||||
|  |                   icon: const Icon(Symbols.refresh), | ||||||
|  |                   onPressed: () { | ||||||
|  |                     memberNotifier.reset(); | ||||||
|  |                     memberNotifier.loadMore(); | ||||||
|  |                     ref.invalidate(memberListProvider); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |                 IconButton( | ||||||
|  |                   icon: const Icon(Symbols.close), | ||||||
|  |                   onPressed: () => Navigator.pop(context), | ||||||
|  |                   style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           const Divider(height: 1), | ||||||
|  |           Expanded( | ||||||
|  |             child: PagingHelperView( | ||||||
|  |               provider: memberListProvider, | ||||||
|  |               futureRefreshable: memberListProvider.future, | ||||||
|  |               notifierRefreshable: memberListProvider.notifier, | ||||||
|  |               contentBuilder: (data, widgetCount, endItemView) { | ||||||
|  |                 return ListView.builder( | ||||||
|  |                   itemCount: widgetCount, | ||||||
|  |                   itemBuilder: (context, index) { | ||||||
|  |                     if (index == data.items.length) { | ||||||
|  |                       return endItemView; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     final member = data.items[index]; | ||||||
|  |                     return ListTile( | ||||||
|  |                       contentPadding: EdgeInsets.only(left: 16, right: 12), | ||||||
|  |                       leading: ProfilePictureWidget( | ||||||
|  |                         fileId: member.account!.profile.picture?.id, | ||||||
|  |                       ), | ||||||
|  |                       title: Row( | ||||||
|  |                         spacing: 6, | ||||||
|  |                         children: [ | ||||||
|  |                           Flexible(child: Text(member.account!.nick)), | ||||||
|  |                           if (member.joinedAt == null) | ||||||
|  |                             const Icon(Symbols.pending_actions, size: 20), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                       subtitle: Row( | ||||||
|  |                         children: [ | ||||||
|  |                           Text( | ||||||
|  |                             member.role >= 100 | ||||||
|  |                                 ? 'permissionOwner' | ||||||
|  |                                 : member.role >= 50 | ||||||
|  |                                 ? 'permissionModerator' | ||||||
|  |                                 : 'permissionMember', | ||||||
|  |                           ).tr(), | ||||||
|  |                           Text('·').bold().padding(horizontal: 6), | ||||||
|  |                           Expanded(child: Text("@${member.account!.name}")), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                       trailing: Row( | ||||||
|  |                         mainAxisSize: MainAxisSize.min, | ||||||
|  |                         children: [ | ||||||
|  |                           if ((publisherIdentity.value?.role ?? 0) >= 50) | ||||||
|  |                             IconButton( | ||||||
|  |                               icon: const Icon(Symbols.edit), | ||||||
|  |                               onPressed: () { | ||||||
|  |                                 showModalBottomSheet( | ||||||
|  |                                   isScrollControlled: true, | ||||||
|  |                                   context: context, | ||||||
|  |                                   builder: | ||||||
|  |                                       (context) => _PublisherMemberRoleSheet( | ||||||
|  |                                         publisherUname: publisherUname, | ||||||
|  |                                         member: member, | ||||||
|  |                                       ), | ||||||
|  |                                 ).then((value) { | ||||||
|  |                                   if (value != null) { | ||||||
|  |                                     ref.invalidate(memberListProvider); | ||||||
|  |                                   } | ||||||
|  |                                 }); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                           if ((publisherIdentity.value?.role ?? 0) >= 50) | ||||||
|  |                             IconButton( | ||||||
|  |                               icon: const Icon(Symbols.delete), | ||||||
|  |                               onPressed: () { | ||||||
|  |                                 showConfirmAlert( | ||||||
|  |                                   'removePublisherMemberHint'.tr(), | ||||||
|  |                                   'removePublisherMember'.tr(), | ||||||
|  |                                 ).then((confirm) async { | ||||||
|  |                                   if (confirm != true) return; | ||||||
|  |                                   try { | ||||||
|  |                                     final apiClient = ref.watch( | ||||||
|  |                                       apiClientProvider, | ||||||
|  |                                     ); | ||||||
|  |                                     await apiClient.delete( | ||||||
|  |                                       '/publishers/$publisherUname/members/${member.accountId}', | ||||||
|  |                                     ); | ||||||
|  |                                     ref.invalidate(memberListProvider); | ||||||
|  |                                   } catch (err) { | ||||||
|  |                                     showErrorAlert(err); | ||||||
|  |                                   } | ||||||
|  |                                 }); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherMemberRoleSheet extends HookConsumerWidget { | ||||||
|  |   final String publisherUname; | ||||||
|  |   final SnPublisherMember member; | ||||||
|  |  | ||||||
|  |   const _PublisherMemberRoleSheet({ | ||||||
|  |     required this.publisherUname, | ||||||
|  |     required this.member, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final roleController = useTextEditingController( | ||||||
|  |       text: member.role.toString(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return Container( | ||||||
|  |       padding: EdgeInsets.only( | ||||||
|  |         bottom: MediaQuery.of(context).viewInsets.bottom, | ||||||
|  |       ), | ||||||
|  |       child: SafeArea( | ||||||
|  |         child: Column( | ||||||
|  |           mainAxisSize: MainAxisSize.min, | ||||||
|  |           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |           children: [ | ||||||
|  |             Padding( | ||||||
|  |               padding: EdgeInsets.only( | ||||||
|  |                 top: 16, | ||||||
|  |                 left: 20, | ||||||
|  |                 right: 16, | ||||||
|  |                 bottom: 12, | ||||||
|  |               ), | ||||||
|  |               child: Row( | ||||||
|  |                 children: [ | ||||||
|  |                   Text( | ||||||
|  |                     'memberRoleEdit'.tr(args: [member.account!.name]), | ||||||
|  |                     style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                       fontWeight: FontWeight.w600, | ||||||
|  |                       letterSpacing: -0.5, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   const Spacer(), | ||||||
|  |                   IconButton( | ||||||
|  |                     icon: const Icon(Symbols.close), | ||||||
|  |                     onPressed: () => Navigator.pop(context), | ||||||
|  |                     style: IconButton.styleFrom( | ||||||
|  |                       minimumSize: const Size(36, 36), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const Divider(height: 1), | ||||||
|  |             Column( | ||||||
|  |               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |               children: [ | ||||||
|  |                 Autocomplete<int>( | ||||||
|  |                   optionsBuilder: (TextEditingValue textEditingValue) { | ||||||
|  |                     if (textEditingValue.text.isEmpty) { | ||||||
|  |                       return const [100, 50, 0]; | ||||||
|  |                     } | ||||||
|  |                     final int? value = int.tryParse(textEditingValue.text); | ||||||
|  |                     if (value == null) return const [100, 50, 0]; | ||||||
|  |                     return [100, 50, 0].where( | ||||||
|  |                       (option) => | ||||||
|  |                           option.toString().contains(textEditingValue.text), | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                   onSelected: (int selection) { | ||||||
|  |                     roleController.text = selection.toString(); | ||||||
|  |                   }, | ||||||
|  |                   fieldViewBuilder: ( | ||||||
|  |                     context, | ||||||
|  |                     controller, | ||||||
|  |                     focusNode, | ||||||
|  |                     onFieldSubmitted, | ||||||
|  |                   ) { | ||||||
|  |                     return TextField( | ||||||
|  |                       controller: controller, | ||||||
|  |                       focusNode: focusNode, | ||||||
|  |                       keyboardType: TextInputType.number, | ||||||
|  |                       decoration: InputDecoration( | ||||||
|  |                         labelText: 'memberRole'.tr(), | ||||||
|  |                         helperText: 'memberRoleHint'.tr(), | ||||||
|  |                       ), | ||||||
|  |                       onTapOutside: (event) => focusNode.unfocus(), | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |                 const Gap(16), | ||||||
|  |                 FilledButton.icon( | ||||||
|  |                   onPressed: () async { | ||||||
|  |                     try { | ||||||
|  |                       final newRole = int.parse(roleController.text); | ||||||
|  |                       if (newRole < 0 || newRole > 100) { | ||||||
|  |                         throw 'Role must be between 0 and 100'; | ||||||
|  |                       } | ||||||
|  |  | ||||||
|  |                       final apiClient = ref.read(apiClientProvider); | ||||||
|  |                       await apiClient.patch( | ||||||
|  |                         '/publishers/$publisherUname/members/${member.accountId}/role', | ||||||
|  |                         data: newRole, | ||||||
|  |                       ); | ||||||
|  |  | ||||||
|  |                       if (context.mounted) Navigator.pop(context, true); | ||||||
|  |                     } catch (err) { | ||||||
|  |                       showErrorAlert(err); | ||||||
|  |                     } | ||||||
|  |                   }, | ||||||
|  |                   icon: const Icon(Symbols.save), | ||||||
|  |                   label: const Text('saveChanges').tr(), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ).padding(vertical: 16, horizontal: 24), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherInviteSheet extends HookConsumerWidget { | ||||||
|  |   const _PublisherInviteSheet(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final invites = ref.watch(publisherInvitesProvider); | ||||||
|  |  | ||||||
|  |     Future<void> acceptInvite(SnPublisherMember invite) async { | ||||||
|  |       try { | ||||||
|  |         final client = ref.read(apiClientProvider); | ||||||
|  |         await client.post( | ||||||
|  |           '/publishers/invites/${invite.publisher!.name}/accept', | ||||||
|  |         ); | ||||||
|  |         ref.invalidate(publisherInvitesProvider); | ||||||
|  |         ref.invalidate(publishersManagedProvider); | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Future<void> declineInvite(SnPublisherMember invite) async { | ||||||
|  |       try { | ||||||
|  |         final client = ref.read(apiClientProvider); | ||||||
|  |         await client.post( | ||||||
|  |           '/publishers/invites/${invite.publisher!.name}/decline', | ||||||
|  |         ); | ||||||
|  |         ref.invalidate(publisherInvitesProvider); | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: 'invites'.tr(), | ||||||
|  |       actions: [ | ||||||
|  |         IconButton( | ||||||
|  |           icon: const Icon(Symbols.refresh), | ||||||
|  |           style: IconButton.styleFrom(minimumSize: const Size(36, 36)), | ||||||
|  |           onPressed: () { | ||||||
|  |             ref.invalidate(publisherInvitesProvider); | ||||||
|  |           }, | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |       child: invites.when( | ||||||
|  |         data: | ||||||
|  |             (items) => | ||||||
|  |                 items.isEmpty | ||||||
|  |                     ? Center( | ||||||
|  |                       child: | ||||||
|  |                           Text( | ||||||
|  |                             'invitesEmpty', | ||||||
|  |                             textAlign: TextAlign.center, | ||||||
|  |                           ).tr(), | ||||||
|  |                     ) | ||||||
|  |                     : ListView.builder( | ||||||
|  |                       shrinkWrap: true, | ||||||
|  |                       itemCount: items.length, | ||||||
|  |                       itemBuilder: (context, index) { | ||||||
|  |                         final invite = items[index]; | ||||||
|  |                         return ListTile( | ||||||
|  |                           leading: ProfilePictureWidget( | ||||||
|  |                             fileId: invite.publisher!.picture?.id, | ||||||
|  |                             fallbackIcon: Symbols.group, | ||||||
|  |                           ), | ||||||
|  |                           title: Text(invite.publisher!.nick), | ||||||
|  |                           subtitle: | ||||||
|  |                               Text( | ||||||
|  |                                 invite.role >= 100 | ||||||
|  |                                     ? 'permissionOwner' | ||||||
|  |                                     : invite.role >= 50 | ||||||
|  |                                     ? 'permissionModerator' | ||||||
|  |                                     : 'permissionMember', | ||||||
|  |                               ).tr(), | ||||||
|  |                           trailing: Row( | ||||||
|  |                             mainAxisSize: MainAxisSize.min, | ||||||
|  |                             children: [ | ||||||
|  |                               IconButton( | ||||||
|  |                                 icon: const Icon(Symbols.check), | ||||||
|  |                                 onPressed: () => acceptInvite(invite), | ||||||
|  |                               ), | ||||||
|  |                               IconButton( | ||||||
|  |                                 icon: const Icon(Symbols.close), | ||||||
|  |                                 onPressed: () => declineInvite(invite), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, _) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(publisherInvitesProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -149,5 +149,422 @@ class _PublisherStatsProviderElement | |||||||
|   String? get uname => (origin as PublisherStatsProvider).uname; |   String? get uname => (origin as PublisherStatsProvider).uname; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | String _$publisherIdentityHash() => r'f7fd986a303a729ca5557022fceb37cd01fa17f3'; | ||||||
|  |  | ||||||
|  | /// See also [publisherIdentity]. | ||||||
|  | @ProviderFor(publisherIdentity) | ||||||
|  | const publisherIdentityProvider = PublisherIdentityFamily(); | ||||||
|  |  | ||||||
|  | /// See also [publisherIdentity]. | ||||||
|  | class PublisherIdentityFamily extends Family<AsyncValue<SnPublisherMember?>> { | ||||||
|  |   /// See also [publisherIdentity]. | ||||||
|  |   const PublisherIdentityFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [publisherIdentity]. | ||||||
|  |   PublisherIdentityProvider call(String uname) { | ||||||
|  |     return PublisherIdentityProvider(uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   PublisherIdentityProvider getProviderOverride( | ||||||
|  |     covariant PublisherIdentityProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'publisherIdentityProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [publisherIdentity]. | ||||||
|  | class PublisherIdentityProvider | ||||||
|  |     extends AutoDisposeFutureProvider<SnPublisherMember?> { | ||||||
|  |   /// See also [publisherIdentity]. | ||||||
|  |   PublisherIdentityProvider(String uname) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => publisherIdentity(ref as PublisherIdentityRef, uname), | ||||||
|  |         from: publisherIdentityProvider, | ||||||
|  |         name: r'publisherIdentityProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$publisherIdentityHash, | ||||||
|  |         dependencies: PublisherIdentityFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             PublisherIdentityFamily._allTransitiveDependencies, | ||||||
|  |         uname: uname, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   PublisherIdentityProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.uname, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String uname; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<SnPublisherMember?> Function(PublisherIdentityRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: PublisherIdentityProvider._internal( | ||||||
|  |         (ref) => create(ref as PublisherIdentityRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         uname: uname, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<SnPublisherMember?> createElement() { | ||||||
|  |     return _PublisherIdentityProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is PublisherIdentityProvider && other.uname == uname; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, uname.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin PublisherIdentityRef on AutoDisposeFutureProviderRef<SnPublisherMember?> { | ||||||
|  |   /// The parameter `uname` of this provider. | ||||||
|  |   String get uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherIdentityProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<SnPublisherMember?> | ||||||
|  |     with PublisherIdentityRef { | ||||||
|  |   _PublisherIdentityProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get uname => (origin as PublisherIdentityProvider).uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$publisherFeaturesHash() => r'34db65d9a4b6b0c6961733ae79e67f25d5d111d3'; | ||||||
|  |  | ||||||
|  | /// See also [publisherFeatures]. | ||||||
|  | @ProviderFor(publisherFeatures) | ||||||
|  | const publisherFeaturesProvider = PublisherFeaturesFamily(); | ||||||
|  |  | ||||||
|  | /// See also [publisherFeatures]. | ||||||
|  | class PublisherFeaturesFamily extends Family<AsyncValue<Map<String, bool>>> { | ||||||
|  |   /// See also [publisherFeatures]. | ||||||
|  |   const PublisherFeaturesFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [publisherFeatures]. | ||||||
|  |   PublisherFeaturesProvider call(String? uname) { | ||||||
|  |     return PublisherFeaturesProvider(uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   PublisherFeaturesProvider getProviderOverride( | ||||||
|  |     covariant PublisherFeaturesProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'publisherFeaturesProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [publisherFeatures]. | ||||||
|  | class PublisherFeaturesProvider | ||||||
|  |     extends AutoDisposeFutureProvider<Map<String, bool>> { | ||||||
|  |   /// See also [publisherFeatures]. | ||||||
|  |   PublisherFeaturesProvider(String? uname) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => publisherFeatures(ref as PublisherFeaturesRef, uname), | ||||||
|  |         from: publisherFeaturesProvider, | ||||||
|  |         name: r'publisherFeaturesProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$publisherFeaturesHash, | ||||||
|  |         dependencies: PublisherFeaturesFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             PublisherFeaturesFamily._allTransitiveDependencies, | ||||||
|  |         uname: uname, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   PublisherFeaturesProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.uname, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String? uname; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<Map<String, bool>> Function(PublisherFeaturesRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: PublisherFeaturesProvider._internal( | ||||||
|  |         (ref) => create(ref as PublisherFeaturesRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         uname: uname, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<Map<String, bool>> createElement() { | ||||||
|  |     return _PublisherFeaturesProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is PublisherFeaturesProvider && other.uname == uname; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, uname.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin PublisherFeaturesRef on AutoDisposeFutureProviderRef<Map<String, bool>> { | ||||||
|  |   /// The parameter `uname` of this provider. | ||||||
|  |   String? get uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherFeaturesProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<Map<String, bool>> | ||||||
|  |     with PublisherFeaturesRef { | ||||||
|  |   _PublisherFeaturesProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get uname => (origin as PublisherFeaturesProvider).uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$publisherInvitesHash() => r'488cd443407895ce11f4edff07cb6ea58f2aa018'; | ||||||
|  |  | ||||||
|  | /// See also [publisherInvites]. | ||||||
|  | @ProviderFor(publisherInvites) | ||||||
|  | final publisherInvitesProvider = | ||||||
|  |     AutoDisposeFutureProvider<List<SnPublisherMember>>.internal( | ||||||
|  |       publisherInvites, | ||||||
|  |       name: r'publisherInvitesProvider', | ||||||
|  |       debugGetCreateSourceHash: | ||||||
|  |           const bool.fromEnvironment('dart.vm.product') | ||||||
|  |               ? null | ||||||
|  |               : _$publisherInvitesHash, | ||||||
|  |       dependencies: null, | ||||||
|  |       allTransitiveDependencies: null, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | typedef PublisherInvitesRef = | ||||||
|  |     AutoDisposeFutureProviderRef<List<SnPublisherMember>>; | ||||||
|  | String _$publisherMemberListNotifierHash() => | ||||||
|  |     r'237e8f39c9757a6cbdff817853c697539242ad2a'; | ||||||
|  |  | ||||||
|  | abstract class _$PublisherMemberListNotifier | ||||||
|  |     extends | ||||||
|  |         BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPublisherMember>> { | ||||||
|  |   late final String uname; | ||||||
|  |  | ||||||
|  |   FutureOr<CursorPagingData<SnPublisherMember>> build(String uname); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [PublisherMemberListNotifier]. | ||||||
|  | @ProviderFor(PublisherMemberListNotifier) | ||||||
|  | const publisherMemberListNotifierProvider = PublisherMemberListNotifierFamily(); | ||||||
|  |  | ||||||
|  | /// See also [PublisherMemberListNotifier]. | ||||||
|  | class PublisherMemberListNotifierFamily | ||||||
|  |     extends Family<AsyncValue<CursorPagingData<SnPublisherMember>>> { | ||||||
|  |   /// See also [PublisherMemberListNotifier]. | ||||||
|  |   const PublisherMemberListNotifierFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [PublisherMemberListNotifier]. | ||||||
|  |   PublisherMemberListNotifierProvider call(String uname) { | ||||||
|  |     return PublisherMemberListNotifierProvider(uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   PublisherMemberListNotifierProvider getProviderOverride( | ||||||
|  |     covariant PublisherMemberListNotifierProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'publisherMemberListNotifierProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [PublisherMemberListNotifier]. | ||||||
|  | class PublisherMemberListNotifierProvider | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderImpl< | ||||||
|  |           PublisherMemberListNotifier, | ||||||
|  |           CursorPagingData<SnPublisherMember> | ||||||
|  |         > { | ||||||
|  |   /// See also [PublisherMemberListNotifier]. | ||||||
|  |   PublisherMemberListNotifierProvider(String uname) | ||||||
|  |     : this._internal( | ||||||
|  |         () => PublisherMemberListNotifier()..uname = uname, | ||||||
|  |         from: publisherMemberListNotifierProvider, | ||||||
|  |         name: r'publisherMemberListNotifierProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$publisherMemberListNotifierHash, | ||||||
|  |         dependencies: PublisherMemberListNotifierFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             PublisherMemberListNotifierFamily._allTransitiveDependencies, | ||||||
|  |         uname: uname, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   PublisherMemberListNotifierProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.uname, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String uname; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   FutureOr<CursorPagingData<SnPublisherMember>> runNotifierBuild( | ||||||
|  |     covariant PublisherMemberListNotifier notifier, | ||||||
|  |   ) { | ||||||
|  |     return notifier.build(uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(PublisherMemberListNotifier Function() create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: PublisherMemberListNotifierProvider._internal( | ||||||
|  |         () => create()..uname = uname, | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         uname: uname, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |     PublisherMemberListNotifier, | ||||||
|  |     CursorPagingData<SnPublisherMember> | ||||||
|  |   > | ||||||
|  |   createElement() { | ||||||
|  |     return _PublisherMemberListNotifierProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is PublisherMemberListNotifierProvider && other.uname == uname; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, uname.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin PublisherMemberListNotifierRef | ||||||
|  |     on | ||||||
|  |         AutoDisposeAsyncNotifierProviderRef< | ||||||
|  |           CursorPagingData<SnPublisherMember> | ||||||
|  |         > { | ||||||
|  |   /// The parameter `uname` of this provider. | ||||||
|  |   String get uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _PublisherMemberListNotifierProviderElement | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |           PublisherMemberListNotifier, | ||||||
|  |           CursorPagingData<SnPublisherMember> | ||||||
|  |         > | ||||||
|  |     with PublisherMemberListNotifierRef { | ||||||
|  |   _PublisherMemberListNotifierProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get uname => (origin as PublisherMemberListNotifierProvider).uname; | ||||||
|  | } | ||||||
|  |  | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
| // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ import 'package:go_router/go_router.dart'; | |||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:image_picker/image_picker.dart'; | import 'package:image_picker/image_picker.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/publisher.dart'; | ||||||
| import 'package:island/models/realm.dart'; | import 'package:island/models/realm.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| @@ -270,7 +270,10 @@ class EditPublisherScreen extends HookConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                     TextFormField( |                     TextFormField( | ||||||
|                       controller: bioController, |                       controller: bioController, | ||||||
|                       decoration: InputDecoration(labelText: 'bio'.tr()), |                       decoration: InputDecoration( | ||||||
|  |                         labelText: 'bio'.tr(), | ||||||
|  |                         alignLabelWithHint: true, | ||||||
|  |                       ), | ||||||
|                       minLines: 3, |                       minLines: 3, | ||||||
|                       maxLines: null, |                       maxLines: null, | ||||||
|                       onTapOutside: |                       onTapOutside: | ||||||
|   | |||||||
| @@ -71,9 +71,7 @@ class SliverStickerPacksList extends HookConsumerWidget { | |||||||
|                 subtitle: Text(sticker.description), |                 subtitle: Text(sticker.description), | ||||||
|                 trailing: const Icon(Symbols.chevron_right), |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|                 onTap: () { |                 onTap: () { | ||||||
|                   context.push( |                   context.push('/creators/$pubName/stickers/${sticker.id}'); | ||||||
|                     '/creators/$pubName/stickers/${sticker.id}', |  | ||||||
|                   ); |  | ||||||
|                 }, |                 }, | ||||||
|               ); |               ); | ||||||
|             }, |             }, | ||||||
| @@ -230,6 +228,7 @@ class EditStickerPacksScreen extends HookConsumerWidget { | |||||||
|                   decoration: InputDecoration( |                   decoration: InputDecoration( | ||||||
|                     labelText: 'description'.tr(), |                     labelText: 'description'.tr(), | ||||||
|                     border: const UnderlineInputBorder(), |                     border: const UnderlineInputBorder(), | ||||||
|  |                     alignLabelWithHint: true, | ||||||
|                   ), |                   ), | ||||||
|                   minLines: 3, |                   minLines: 3, | ||||||
|                   maxLines: null, |                   maxLines: null, | ||||||
|   | |||||||
							
								
								
									
										287
									
								
								lib/screens/creators/webfeed/webfeed_edit.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								lib/screens/creators/webfeed/webfeed_edit.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/webfeed.dart'; | ||||||
|  | import 'package:island/pods/webfeed.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | class WebFeedNewScreen extends StatelessWidget { | ||||||
|  |   final String pubName; | ||||||
|  |   const WebFeedNewScreen({super.key, required this.pubName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return WebFeedEditScreen(pubName: pubName, feedId: null); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class WebFeedEditScreen extends HookConsumerWidget { | ||||||
|  |   final String pubName; | ||||||
|  |   final String? feedId; | ||||||
|  |  | ||||||
|  |   const WebFeedEditScreen({super.key, required this.pubName, this.feedId}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |     final titleController = useTextEditingController(); | ||||||
|  |     final urlController = useTextEditingController(); | ||||||
|  |     final descriptionController = useTextEditingController(); | ||||||
|  |     final isLoading = useState(false); | ||||||
|  |     final isScrapEnabled = useState(false); | ||||||
|  |  | ||||||
|  |     final saveFeed = useCallback(() async { | ||||||
|  |       if (!formKey.currentState!.validate()) return; | ||||||
|  |  | ||||||
|  |       isLoading.value = true; | ||||||
|  |  | ||||||
|  |       try { | ||||||
|  |         final feed = WebFeed( | ||||||
|  |           id: feedId ?? '', | ||||||
|  |           title: titleController.text, | ||||||
|  |           url: urlController.text, | ||||||
|  |           description: descriptionController.text, | ||||||
|  |           config: WebFeedConfig(scrapPage: isScrapEnabled.value), | ||||||
|  |           publisherId: pubName, | ||||||
|  |           createdAt: DateTime.now(), | ||||||
|  |           updatedAt: DateTime.now(), | ||||||
|  |           deletedAt: null, | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         await ref | ||||||
|  |             .read( | ||||||
|  |               webFeedNotifierProvider(( | ||||||
|  |                 pubName: pubName, | ||||||
|  |                 feedId: feedId, | ||||||
|  |               )).notifier, | ||||||
|  |             ) | ||||||
|  |             .saveFeed(feed); | ||||||
|  |  | ||||||
|  |         // Refresh the feed list | ||||||
|  |         ref.invalidate(webFeedListProvider(pubName)); | ||||||
|  |  | ||||||
|  |         if (context.mounted) { | ||||||
|  |           showSnackBar('Web feed saved successfully'); | ||||||
|  |           context.pop(); | ||||||
|  |         } | ||||||
|  |       } catch (e) { | ||||||
|  |         showErrorAlert(e); | ||||||
|  |       } finally { | ||||||
|  |         isLoading.value = false; | ||||||
|  |       } | ||||||
|  |     }, [pubName, feedId, isScrapEnabled.value, context]); | ||||||
|  |  | ||||||
|  |     final deleteFeed = useCallback(() async { | ||||||
|  |       final confirmed = await showConfirmAlert( | ||||||
|  |         'Are you sure you want to delete this web feed? This action cannot be undone.', | ||||||
|  |         'Delete Web Feed', | ||||||
|  |       ); | ||||||
|  |       if (confirmed != true) return; | ||||||
|  |  | ||||||
|  |       isLoading.value = true; | ||||||
|  |  | ||||||
|  |       try { | ||||||
|  |         await ref | ||||||
|  |             .read( | ||||||
|  |               webFeedNotifierProvider(( | ||||||
|  |                 pubName: pubName, | ||||||
|  |                 feedId: feedId!, | ||||||
|  |               )).notifier, | ||||||
|  |             ) | ||||||
|  |             .deleteFeed(); | ||||||
|  |  | ||||||
|  |         ref.invalidate(webFeedListProvider(pubName)); | ||||||
|  |  | ||||||
|  |         if (context.mounted) { | ||||||
|  |           showSnackBar('Web feed deleted successfully'); | ||||||
|  |           context.pop(); | ||||||
|  |         } | ||||||
|  |       } catch (e) { | ||||||
|  |         showErrorAlert(e); | ||||||
|  |       } finally { | ||||||
|  |         isLoading.value = false; | ||||||
|  |       } | ||||||
|  |     }, [pubName, feedId, context, ref]); | ||||||
|  |  | ||||||
|  |     final feedAsync = ref.watch( | ||||||
|  |       webFeedNotifierProvider((pubName: pubName, feedId: feedId)), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return feedAsync.when( | ||||||
|  |       loading: | ||||||
|  |           () => | ||||||
|  |               const Scaffold(body: Center(child: CircularProgressIndicator())), | ||||||
|  |       error: | ||||||
|  |           (error, stack) => Scaffold( | ||||||
|  |             appBar: AppBar(title: const Text('Error')), | ||||||
|  |             body: Center(child: Text('Error: $error')), | ||||||
|  |           ), | ||||||
|  |       data: (feed) { | ||||||
|  |         // Initialize form fields if they're empty and we have a feed | ||||||
|  |         if (titleController.text.isEmpty) { | ||||||
|  |           titleController.text = feed.title; | ||||||
|  |           urlController.text = feed.url; | ||||||
|  |           descriptionController.text = feed.description ?? ''; | ||||||
|  |           isScrapEnabled.value = feed.config.scrapPage; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return _buildForm( | ||||||
|  |           context, | ||||||
|  |           formKey: formKey, | ||||||
|  |           titleController: titleController, | ||||||
|  |           urlController: urlController, | ||||||
|  |           descriptionController: descriptionController, | ||||||
|  |           isScrapEnabled: isScrapEnabled.value, | ||||||
|  |           onScrapEnabledChanged: (value) => isScrapEnabled.value = value, | ||||||
|  |           onSave: saveFeed, | ||||||
|  |           onDelete: deleteFeed, | ||||||
|  |           isLoading: isLoading.value, | ||||||
|  |           ref: ref, | ||||||
|  |           hasFeedId: feedId != null, | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget _buildForm( | ||||||
|  |     BuildContext context, { | ||||||
|  |     required WidgetRef ref, | ||||||
|  |     required GlobalKey<FormState> formKey, | ||||||
|  |     required TextEditingController titleController, | ||||||
|  |     required TextEditingController urlController, | ||||||
|  |     required TextEditingController descriptionController, | ||||||
|  |     required bool isScrapEnabled, | ||||||
|  |     required ValueChanged<bool> onScrapEnabledChanged, | ||||||
|  |     required VoidCallback onSave, | ||||||
|  |     required VoidCallback onDelete, | ||||||
|  |     required bool isLoading, | ||||||
|  |     required bool hasFeedId, | ||||||
|  |   }) { | ||||||
|  |     final scrapNow = useCallback(() async { | ||||||
|  |       showLoadingModal(context); | ||||||
|  |       try { | ||||||
|  |         await ref | ||||||
|  |             .read( | ||||||
|  |               webFeedNotifierProvider(( | ||||||
|  |                 pubName: pubName, | ||||||
|  |                 feedId: feedId!, | ||||||
|  |               )).notifier, | ||||||
|  |             ) | ||||||
|  |             .scrapFeed(); | ||||||
|  |  | ||||||
|  |         if (context.mounted) { | ||||||
|  |           showSnackBar('Feed scraping successfully.'); | ||||||
|  |         } | ||||||
|  |       } catch (e) { | ||||||
|  |         showErrorAlert(e); | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |       } | ||||||
|  |     }, [pubName, feedId, ref, context]); | ||||||
|  |  | ||||||
|  |     return Scaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'), | ||||||
|  |         actions: [ | ||||||
|  |           if (hasFeedId) | ||||||
|  |             IconButton( | ||||||
|  |               icon: const Icon(Symbols.delete_forever), | ||||||
|  |               onPressed: isLoading ? null : onDelete, | ||||||
|  |             ), | ||||||
|  |           const SizedBox(width: 8), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       body: Form( | ||||||
|  |         key: formKey, | ||||||
|  |         child: SingleChildScrollView( | ||||||
|  |           child: Column( | ||||||
|  |             children: [ | ||||||
|  |               TextFormField( | ||||||
|  |                 controller: titleController, | ||||||
|  |                 decoration: const InputDecoration(labelText: 'Title'), | ||||||
|  |                 validator: (value) { | ||||||
|  |                   if (value == null || value.isEmpty) { | ||||||
|  |                     return 'Please enter a title'; | ||||||
|  |                   } | ||||||
|  |                   return null; | ||||||
|  |                 }, | ||||||
|  |                 onTapOutside: | ||||||
|  |                     (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |               ), | ||||||
|  |               const SizedBox(height: 16), | ||||||
|  |               TextFormField( | ||||||
|  |                 controller: urlController, | ||||||
|  |                 decoration: const InputDecoration( | ||||||
|  |                   labelText: 'URL', | ||||||
|  |                   hintText: 'https://example.com/feed', | ||||||
|  |                 ), | ||||||
|  |                 keyboardType: TextInputType.url, | ||||||
|  |                 validator: (value) { | ||||||
|  |                   if (value == null || value.isEmpty) { | ||||||
|  |                     return 'Please enter a URL'; | ||||||
|  |                   } | ||||||
|  |                   final uri = Uri.tryParse(value); | ||||||
|  |                   if (uri == null || !uri.hasAbsolutePath) { | ||||||
|  |                     return 'Please enter a valid URL'; | ||||||
|  |                   } | ||||||
|  |                   return null; | ||||||
|  |                 }, | ||||||
|  |                 onTapOutside: | ||||||
|  |                     (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |               ), | ||||||
|  |               const SizedBox(height: 16), | ||||||
|  |               TextFormField( | ||||||
|  |                 controller: descriptionController, | ||||||
|  |                 decoration: const InputDecoration( | ||||||
|  |                   labelText: 'Description', | ||||||
|  |                   alignLabelWithHint: true, | ||||||
|  |                 ), | ||||||
|  |                 onTapOutside: | ||||||
|  |                     (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                 maxLines: 3, | ||||||
|  |               ), | ||||||
|  |               const SizedBox(height: 24), | ||||||
|  |               Card( | ||||||
|  |                 margin: EdgeInsets.zero, | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                   children: [ | ||||||
|  |                     SwitchListTile( | ||||||
|  |                       title: const Text('Scrape web page for content'), | ||||||
|  |                       subtitle: const Text( | ||||||
|  |                         'When enabled, the system will attempt to extract full content from the web page', | ||||||
|  |                       ), | ||||||
|  |                       shape: RoundedRectangleBorder( | ||||||
|  |                         borderRadius: BorderRadius.circular(8), | ||||||
|  |                       ), | ||||||
|  |                       value: isScrapEnabled, | ||||||
|  |                       onChanged: onScrapEnabledChanged, | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               const SizedBox(height: 20), | ||||||
|  |               if (hasFeedId) ...[ | ||||||
|  |                 FilledButton.tonalIcon( | ||||||
|  |                   onPressed: isLoading ? null : scrapNow, | ||||||
|  |                   icon: const Icon(Symbols.refresh), | ||||||
|  |                   label: const Text('Scrape Now'), | ||||||
|  |                 ).alignment(Alignment.centerRight), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|  |               ], | ||||||
|  |               FilledButton.icon( | ||||||
|  |                 onPressed: isLoading ? null : onSave, | ||||||
|  |                 icon: const Icon(Symbols.save), | ||||||
|  |                 label: Text('saveChanges').tr(), | ||||||
|  |               ).alignment(Alignment.centerRight), | ||||||
|  |             ], | ||||||
|  |           ).padding(all: 20), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								lib/screens/creators/webfeed/webfeed_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								lib/screens/creators/webfeed/webfeed_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:island/pods/webfeed.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/empty_state.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
|  | class WebFeedListScreen extends ConsumerWidget { | ||||||
|  |   final String pubName; | ||||||
|  |  | ||||||
|  |   const WebFeedListScreen({super.key, required this.pubName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final feedsAsync = ref.watch(webFeedListProvider(pubName)); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('Web Feeds')), | ||||||
|  |       floatingActionButton: FloatingActionButton( | ||||||
|  |         child: const Icon(Symbols.add), | ||||||
|  |         onPressed: () { | ||||||
|  |           context.push('/creators/$pubName/feeds/new'); | ||||||
|  |         }, | ||||||
|  |       ), | ||||||
|  |       body: feedsAsync.when( | ||||||
|  |         data: (feeds) { | ||||||
|  |           if (feeds.isEmpty) { | ||||||
|  |             return EmptyState( | ||||||
|  |               icon: Symbols.rss_feed, | ||||||
|  |               title: 'No Web Feeds', | ||||||
|  |               description: 'Add a new web feed to get started', | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |           return RefreshIndicator( | ||||||
|  |             onRefresh: () => ref.refresh(webFeedListProvider(pubName).future), | ||||||
|  |             child: ListView.builder( | ||||||
|  |               padding: EdgeInsets.only(top: 8), | ||||||
|  |               itemCount: feeds.length, | ||||||
|  |               itemBuilder: (context, index) { | ||||||
|  |                 final feed = feeds[index]; | ||||||
|  |                 return Card( | ||||||
|  |                   margin: const EdgeInsets.symmetric( | ||||||
|  |                     horizontal: 12, | ||||||
|  |                     vertical: 4, | ||||||
|  |                   ), | ||||||
|  |                   child: ListTile( | ||||||
|  |                     leading: const Icon(Symbols.rss_feed, size: 32), | ||||||
|  |                     shape: RoundedRectangleBorder( | ||||||
|  |                       borderRadius: BorderRadius.circular(8), | ||||||
|  |                     ), | ||||||
|  |                     title: Text( | ||||||
|  |                       feed.title, | ||||||
|  |                       style: Theme.of(context).textTheme.titleMedium, | ||||||
|  |                       maxLines: 1, | ||||||
|  |                       overflow: TextOverflow.ellipsis, | ||||||
|  |                     ), | ||||||
|  |                     subtitle: Text( | ||||||
|  |                       feed.url, | ||||||
|  |                       maxLines: 1, | ||||||
|  |                       overflow: TextOverflow.ellipsis, | ||||||
|  |                     ), | ||||||
|  |                     trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                     onTap: () { | ||||||
|  |                       context.push('/creators/$pubName/feeds/${feed.id}'); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: (error, _) => Center(child: Text('Error: $error')), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										162
									
								
								lib/screens/developers/apps.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								lib/screens/developers/apps.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:google_fonts/google_fonts.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/custom_app.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'apps.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<CustomApp>> customApps(Ref ref, String publisherName) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/developers/$publisherName/apps'); | ||||||
|  |   return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class CustomAppsScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   const CustomAppsScreen({super.key, required this.publisherName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final apps = ref.watch(customAppsProvider(publisherName)); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text('customApps').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.add), | ||||||
|  |             onPressed: () { | ||||||
|  |               context.push('/developers/$publisherName/apps/new'); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       body: apps.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.isEmpty) { | ||||||
|  |             return Center(child: Text('noCustomApps').tr()); | ||||||
|  |           } | ||||||
|  |           return RefreshIndicator( | ||||||
|  |             onRefresh: | ||||||
|  |                 () => ref.refresh(customAppsProvider(publisherName).future), | ||||||
|  |             child: ListView.builder( | ||||||
|  |               padding: EdgeInsets.only(top: 4), | ||||||
|  |               itemCount: data.length, | ||||||
|  |               itemBuilder: (context, index) { | ||||||
|  |                 final app = data[index]; | ||||||
|  |                 return Card( | ||||||
|  |                   margin: const EdgeInsets.all(8.0), | ||||||
|  |                   child: Column( | ||||||
|  |                     children: [ | ||||||
|  |                       SizedBox( | ||||||
|  |                         height: 150, | ||||||
|  |                         child: Stack( | ||||||
|  |                           fit: StackFit.expand, | ||||||
|  |                           children: [ | ||||||
|  |                             if (app.background != null) | ||||||
|  |                               CloudFileWidget( | ||||||
|  |                                 item: app.background!, | ||||||
|  |                                 fit: BoxFit.cover, | ||||||
|  |                               ).clipRRect(topLeft: 8, topRight: 8), | ||||||
|  |                             if (app.picture != null) | ||||||
|  |                               Positioned( | ||||||
|  |                                 left: 16, | ||||||
|  |                                 bottom: 16, | ||||||
|  |                                 child: ProfilePictureWidget( | ||||||
|  |                                   fileId: app.picture!.id, | ||||||
|  |                                   radius: 40, | ||||||
|  |                                   fallbackIcon: Symbols.apps, | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                       ListTile( | ||||||
|  |                         title: Text(app.name), | ||||||
|  |                         subtitle: Text( | ||||||
|  |                           app.slug, | ||||||
|  |                           style: GoogleFonts.robotoMono(fontSize: 12), | ||||||
|  |                         ), | ||||||
|  |                         contentPadding: EdgeInsets.only(left: 20, right: 12), | ||||||
|  |                         trailing: PopupMenuButton( | ||||||
|  |                           itemBuilder: | ||||||
|  |                               (context) => [ | ||||||
|  |                                 PopupMenuItem( | ||||||
|  |                                   value: 'edit', | ||||||
|  |                                   child: Row( | ||||||
|  |                                     children: [ | ||||||
|  |                                       const Icon(Symbols.edit), | ||||||
|  |                                       const SizedBox(width: 12), | ||||||
|  |                                       Text('edit').tr(), | ||||||
|  |                                     ], | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                                 PopupMenuItem( | ||||||
|  |                                   value: 'delete', | ||||||
|  |                                   child: Row( | ||||||
|  |                                     children: [ | ||||||
|  |                                       const Icon( | ||||||
|  |                                         Symbols.delete, | ||||||
|  |                                         color: Colors.red, | ||||||
|  |                                       ), | ||||||
|  |                                       const SizedBox(width: 12), | ||||||
|  |                                       Text( | ||||||
|  |                                         'delete', | ||||||
|  |                                         style: TextStyle(color: Colors.red), | ||||||
|  |                                       ).tr(), | ||||||
|  |                                     ], | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ], | ||||||
|  |                           onSelected: (value) { | ||||||
|  |                             if (value == 'edit') { | ||||||
|  |                               context.push( | ||||||
|  |                                 '/developers/$publisherName/apps/${app.id}', | ||||||
|  |                               ); | ||||||
|  |                             } else if (value == 'delete') { | ||||||
|  |                               showConfirmAlert( | ||||||
|  |                                 'deleteCustomAppHint'.tr(), | ||||||
|  |                                 'deleteCustomApp'.tr(), | ||||||
|  |                               ).then((confirm) { | ||||||
|  |                                 if (confirm) { | ||||||
|  |                                   final client = ref.read(apiClientProvider); | ||||||
|  |                                   client.delete( | ||||||
|  |                                     '/developers/$publisherName/apps/${app.id}', | ||||||
|  |                                   ); | ||||||
|  |                                   ref.invalidate( | ||||||
|  |                                     customAppsProvider(publisherName), | ||||||
|  |                                   ); | ||||||
|  |                                 } | ||||||
|  |                               }); | ||||||
|  |                             } | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (err, stack) => ResponseErrorWidget( | ||||||
|  |               error: err, | ||||||
|  |               onRetry: () => ref.invalidate(customAppsProvider(publisherName)), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										151
									
								
								lib/screens/developers/apps.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								lib/screens/developers/apps.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'apps.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$customAppsHash() => r'1dec11573b9d987c3adbdf4732b3781a6f40172a'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [customApps]. | ||||||
|  | @ProviderFor(customApps) | ||||||
|  | const customAppsProvider = CustomAppsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [customApps]. | ||||||
|  | class CustomAppsFamily extends Family<AsyncValue<List<CustomApp>>> { | ||||||
|  |   /// See also [customApps]. | ||||||
|  |   const CustomAppsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [customApps]. | ||||||
|  |   CustomAppsProvider call(String publisherName) { | ||||||
|  |     return CustomAppsProvider(publisherName); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   CustomAppsProvider getProviderOverride( | ||||||
|  |     covariant CustomAppsProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.publisherName); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'customAppsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [customApps]. | ||||||
|  | class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | ||||||
|  |   /// See also [customApps]. | ||||||
|  |   CustomAppsProvider(String publisherName) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => customApps(ref as CustomAppsRef, publisherName), | ||||||
|  |         from: customAppsProvider, | ||||||
|  |         name: r'customAppsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$customAppsHash, | ||||||
|  |         dependencies: CustomAppsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: CustomAppsFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   CustomAppsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<CustomApp>> Function(CustomAppsRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: CustomAppsProvider._internal( | ||||||
|  |         (ref) => create(ref as CustomAppsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<CustomApp>> createElement() { | ||||||
|  |     return _CustomAppsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is CustomAppsProvider && other.publisherName == publisherName; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin CustomAppsRef on AutoDisposeFutureProviderRef<List<CustomApp>> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _CustomAppsProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<CustomApp>> | ||||||
|  |     with CustomAppsRef { | ||||||
|  |   _CustomAppsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as CustomAppsProvider).publisherName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
							
								
								
									
										558
									
								
								lib/screens/developers/edit_app.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										558
									
								
								lib/screens/developers/edit_app.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,558 @@ | |||||||
|  | import 'package:croppy/croppy.dart' hide cropImage; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:image_picker/image_picker.dart'; | ||||||
|  | import 'package:island/models/custom_app.dart'; | ||||||
|  | import 'package:island/models/file.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/screens/developers/apps.dart'; | ||||||
|  | import 'package:island/services/file.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  |  | ||||||
|  | part 'edit_app.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/developers/$publisherName/apps/$id'); | ||||||
|  |   return CustomApp.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class EditAppScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String? id; | ||||||
|  |   const EditAppScreen({super.key, required this.publisherName, this.id}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final isNew = id == null; | ||||||
|  |     final app = isNew ? null : ref.watch(customAppProvider(publisherName, id!)); | ||||||
|  |  | ||||||
|  |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |  | ||||||
|  |     final submitting = useState(false); | ||||||
|  |  | ||||||
|  |     final nameController = useTextEditingController(); | ||||||
|  |     final slugController = useTextEditingController(); | ||||||
|  |     final descriptionController = useTextEditingController(); | ||||||
|  |     final picture = useState<SnCloudFile?>(null); | ||||||
|  |     final background = useState<SnCloudFile?>(null); | ||||||
|  |  | ||||||
|  |     final enableLinks = useState(false); // Only for UI purposes | ||||||
|  |     final homePageController = useTextEditingController(); | ||||||
|  |     final privacyPolicyController = useTextEditingController(); | ||||||
|  |     final termsController = useTextEditingController(); | ||||||
|  |     final oauthEnabled = useState(false); | ||||||
|  |     final redirectUris = useState<List<String>>([]); | ||||||
|  |     final postLogoutUris = useState<List<String>>([]); | ||||||
|  |     final allowedScopes = useState<List<String>>([ | ||||||
|  |       'openid', | ||||||
|  |       'profile', | ||||||
|  |       'email', | ||||||
|  |     ]); | ||||||
|  |     final allowedGrantTypes = useState<List<String>>([ | ||||||
|  |       'authorization_code', | ||||||
|  |       'refresh_token', | ||||||
|  |     ]); | ||||||
|  |     final requirePkce = useState(true); | ||||||
|  |     final allowOfflineAccess = useState(false); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       if (app?.value != null) { | ||||||
|  |         nameController.text = app!.value!.name; | ||||||
|  |         slugController.text = app.value!.slug; | ||||||
|  |         descriptionController.text = app.value!.description ?? ''; | ||||||
|  |         picture.value = app.value!.picture; | ||||||
|  |         background.value = app.value!.background; | ||||||
|  |         homePageController.text = app.value!.links?.homePage ?? ''; | ||||||
|  |         privacyPolicyController.text = app.value!.links?.privacyPolicy ?? ''; | ||||||
|  |         termsController.text = app.value!.links?.termsOfService ?? ''; | ||||||
|  |         if (app.value!.oauthConfig != null) { | ||||||
|  |           oauthEnabled.value = true; | ||||||
|  |           redirectUris.value = app.value!.oauthConfig!.redirectUris; | ||||||
|  |           postLogoutUris.value = | ||||||
|  |               app.value!.oauthConfig!.postLogoutRedirectUris ?? []; | ||||||
|  |           allowedScopes.value = app.value!.oauthConfig!.allowedScopes; | ||||||
|  |           allowedGrantTypes.value = app.value!.oauthConfig!.allowedGrantTypes; | ||||||
|  |           requirePkce.value = app.value!.oauthConfig!.requirePkce; | ||||||
|  |           allowOfflineAccess.value = app.value!.oauthConfig!.allowOfflineAccess; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [app]); | ||||||
|  |  | ||||||
|  |     void setPicture(String position) async { | ||||||
|  |       showLoadingModal(context); | ||||||
|  |       var result = await ref | ||||||
|  |           .read(imagePickerProvider) | ||||||
|  |           .pickImage(source: ImageSource.gallery); | ||||||
|  |       if (result == null) { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       hideLoadingModal(context); | ||||||
|  |       result = await cropImage( | ||||||
|  |         context, | ||||||
|  |         image: result, | ||||||
|  |         allowedAspectRatios: [ | ||||||
|  |           if (position == 'background') | ||||||
|  |             const CropAspectRatio(height: 7, width: 16) | ||||||
|  |           else | ||||||
|  |             const CropAspectRatio(height: 1, width: 1), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |       if (result == null) { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       showLoadingModal(context); | ||||||
|  |  | ||||||
|  |       submitting.value = true; | ||||||
|  |       try { | ||||||
|  |         final baseUrl = ref.watch(serverUrlProvider); | ||||||
|  |         final token = await getToken(ref.watch(tokenProvider)); | ||||||
|  |         if (token == null) throw ArgumentError('Token is null'); | ||||||
|  |         final cloudFile = | ||||||
|  |             await putMediaToCloud( | ||||||
|  |               fileData: UniversalFile( | ||||||
|  |                 data: result, | ||||||
|  |                 type: UniversalFileType.image, | ||||||
|  |               ), | ||||||
|  |               atk: token, | ||||||
|  |               baseUrl: baseUrl, | ||||||
|  |               filename: result.name, | ||||||
|  |               mimetype: result.mimeType ?? 'image/jpeg', | ||||||
|  |             ).future; | ||||||
|  |         if (cloudFile == null) { | ||||||
|  |           throw ArgumentError('Failed to upload the file...'); | ||||||
|  |         } | ||||||
|  |         switch (position) { | ||||||
|  |           case 'picture': | ||||||
|  |             picture.value = cloudFile; | ||||||
|  |           case 'background': | ||||||
|  |             background.value = cloudFile; | ||||||
|  |         } | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         submitting.value = false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void showAddScopeDialog() { | ||||||
|  |       final scopeController = TextEditingController(); | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: | ||||||
|  |             (context) => SheetScaffold( | ||||||
|  |               titleText: 'addScope'.tr(), | ||||||
|  |               child: Padding( | ||||||
|  |                 padding: const EdgeInsets.all(20), | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                   children: [ | ||||||
|  |                     TextFormField( | ||||||
|  |                       controller: scopeController, | ||||||
|  |                       decoration: InputDecoration(labelText: 'scopeName'.tr()), | ||||||
|  |                     ), | ||||||
|  |                     const SizedBox(height: 20), | ||||||
|  |                     FilledButton.tonalIcon( | ||||||
|  |                       onPressed: () { | ||||||
|  |                         if (scopeController.text.isNotEmpty) { | ||||||
|  |                           allowedScopes.value = [ | ||||||
|  |                             ...allowedScopes.value, | ||||||
|  |                             scopeController.text, | ||||||
|  |                           ]; | ||||||
|  |                           Navigator.pop(context); | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                       icon: const Icon(Symbols.add), | ||||||
|  |                       label: Text('add').tr(), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void showAddRedirectUriDialog() { | ||||||
|  |       final uriController = TextEditingController(); | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: | ||||||
|  |             (context) => SheetScaffold( | ||||||
|  |               titleText: 'addRedirectUri'.tr(), | ||||||
|  |               child: Padding( | ||||||
|  |                 padding: const EdgeInsets.all(20), | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                   children: [ | ||||||
|  |                     TextFormField( | ||||||
|  |                       controller: uriController, | ||||||
|  |                       decoration: InputDecoration( | ||||||
|  |                         labelText: 'redirectUri'.tr(), | ||||||
|  |                         hintText: 'https://example.com/auth/callback', | ||||||
|  |                         helperText: 'redirectUriHint'.tr(), | ||||||
|  |                         helperMaxLines: 3, | ||||||
|  |                       ), | ||||||
|  |                       keyboardType: TextInputType.url, | ||||||
|  |                       validator: (value) { | ||||||
|  |                         if (value == null || value.isEmpty) { | ||||||
|  |                           return 'uriRequired'.tr(); | ||||||
|  |                         } | ||||||
|  |                         final uri = Uri.tryParse(value); | ||||||
|  |                         if (uri == null || !uri.hasAbsolutePath) { | ||||||
|  |                           return 'invalidUri'.tr(); | ||||||
|  |                         } | ||||||
|  |                         return null; | ||||||
|  |                       }, | ||||||
|  |                       onTapOutside: | ||||||
|  |                           (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                     ), | ||||||
|  |                     const SizedBox(height: 20), | ||||||
|  |                     FilledButton.tonalIcon( | ||||||
|  |                       onPressed: () { | ||||||
|  |                         if (uriController.text.isNotEmpty) { | ||||||
|  |                           redirectUris.value = [ | ||||||
|  |                             ...redirectUris.value, | ||||||
|  |                             uriController.text, | ||||||
|  |                           ]; | ||||||
|  |                           Navigator.pop(context); | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                       icon: const Icon(Symbols.add), | ||||||
|  |                       label: Text('add').tr(), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void performAction() async { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final data = { | ||||||
|  |         'name': nameController.text, | ||||||
|  |         'slug': slugController.text, | ||||||
|  |         'description': descriptionController.text, | ||||||
|  |         'picture_id': picture.value?.id, | ||||||
|  |         'background_id': background.value?.id, | ||||||
|  |         'links': { | ||||||
|  |           'home_page': | ||||||
|  |               homePageController.text.isNotEmpty | ||||||
|  |                   ? homePageController.text | ||||||
|  |                   : null, | ||||||
|  |           'privacy_policy': | ||||||
|  |               privacyPolicyController.text.isNotEmpty | ||||||
|  |                   ? privacyPolicyController.text | ||||||
|  |                   : null, | ||||||
|  |           'terms_of_service': | ||||||
|  |               termsController.text.isNotEmpty ? termsController.text : null, | ||||||
|  |         }, | ||||||
|  |         'oauth_config': | ||||||
|  |             oauthEnabled.value | ||||||
|  |                 ? { | ||||||
|  |                   'redirect_uris': redirectUris.value, | ||||||
|  |                   'post_logout_redirect_uris': | ||||||
|  |                       postLogoutUris.value.isNotEmpty | ||||||
|  |                           ? postLogoutUris.value | ||||||
|  |                           : null, | ||||||
|  |                   'allowed_scopes': allowedScopes.value, | ||||||
|  |                   'allowed_grant_types': allowedGrantTypes.value, | ||||||
|  |                   'require_pkce': requirePkce.value, | ||||||
|  |                   'allow_offline_access': allowOfflineAccess.value, | ||||||
|  |                 } | ||||||
|  |                 : null, | ||||||
|  |       }; | ||||||
|  |       if (isNew) { | ||||||
|  |         await client.post('/developers/$publisherName/apps', data: data); | ||||||
|  |       } else { | ||||||
|  |         await client.patch('/developers/$publisherName/apps/$id', data: data); | ||||||
|  |       } | ||||||
|  |       ref.invalidate(customAppsProvider(publisherName)); | ||||||
|  |       if (context.mounted) { | ||||||
|  |         Navigator.pop(context); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(isNew ? 'createCustomApp'.tr() : 'editCustomApp'.tr()), | ||||||
|  |       ), | ||||||
|  |       body: | ||||||
|  |           app == null && !isNew | ||||||
|  |               ? const Center(child: CircularProgressIndicator()) | ||||||
|  |               : app?.hasError == true && !isNew | ||||||
|  |               ? ResponseErrorWidget( | ||||||
|  |                 error: app!.error, | ||||||
|  |                 onRetry: | ||||||
|  |                     () => ref.invalidate(customAppProvider(publisherName, id!)), | ||||||
|  |               ) | ||||||
|  |               : SingleChildScrollView( | ||||||
|  |                 child: Column( | ||||||
|  |                   children: [ | ||||||
|  |                     AspectRatio( | ||||||
|  |                       aspectRatio: 16 / 7, | ||||||
|  |                       child: Stack( | ||||||
|  |                         clipBehavior: Clip.none, | ||||||
|  |                         fit: StackFit.expand, | ||||||
|  |                         children: [ | ||||||
|  |                           GestureDetector( | ||||||
|  |                             child: Container( | ||||||
|  |                               color: | ||||||
|  |                                   Theme.of( | ||||||
|  |                                     context, | ||||||
|  |                                   ).colorScheme.surfaceContainerHigh, | ||||||
|  |                               child: | ||||||
|  |                                   background.value != null | ||||||
|  |                                       ? CloudFileWidget( | ||||||
|  |                                         item: background.value!, | ||||||
|  |                                         fit: BoxFit.cover, | ||||||
|  |                                       ) | ||||||
|  |                                       : const SizedBox.shrink(), | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               setPicture('background'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           Positioned( | ||||||
|  |                             left: 20, | ||||||
|  |                             bottom: -32, | ||||||
|  |                             child: GestureDetector( | ||||||
|  |                               child: ProfilePictureWidget( | ||||||
|  |                                 fileId: picture.value?.id, | ||||||
|  |                                 radius: 40, | ||||||
|  |                                 fallbackIcon: Symbols.apps, | ||||||
|  |                               ), | ||||||
|  |                               onTap: () { | ||||||
|  |                                 setPicture('picture'); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ).padding(bottom: 32), | ||||||
|  |                     Form( | ||||||
|  |                       key: formKey, | ||||||
|  |                       child: Column( | ||||||
|  |                         children: [ | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: nameController, | ||||||
|  |                             decoration: InputDecoration(labelText: 'name'.tr()), | ||||||
|  |                             onTapOutside: | ||||||
|  |                                 (_) => | ||||||
|  |                                     FocusManager.instance.primaryFocus | ||||||
|  |                                         ?.unfocus(), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: slugController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'slug'.tr(), | ||||||
|  |                               helperText: 'slugHint'.tr(), | ||||||
|  |                             ), | ||||||
|  |                             onTapOutside: | ||||||
|  |                                 (_) => | ||||||
|  |                                     FocusManager.instance.primaryFocus | ||||||
|  |                                         ?.unfocus(), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: descriptionController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'description'.tr(), | ||||||
|  |                               alignLabelWithHint: true, | ||||||
|  |                             ), | ||||||
|  |                             maxLines: 3, | ||||||
|  |                             onTapOutside: | ||||||
|  |                                 (_) => | ||||||
|  |                                     FocusManager.instance.primaryFocus | ||||||
|  |                                         ?.unfocus(), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           ExpansionPanelList( | ||||||
|  |                             expansionCallback: (index, isExpanded) { | ||||||
|  |                               switch (index) { | ||||||
|  |                                 case 0: | ||||||
|  |                                   enableLinks.value = isExpanded; | ||||||
|  |                                   break; | ||||||
|  |                                 case 1: | ||||||
|  |                                   oauthEnabled.value = isExpanded; | ||||||
|  |                                   break; | ||||||
|  |                               } | ||||||
|  |                             }, | ||||||
|  |                             children: [ | ||||||
|  |                               ExpansionPanel( | ||||||
|  |                                 headerBuilder: | ||||||
|  |                                     (context, isExpanded) => | ||||||
|  |                                         ListTile(title: Text('appLinks').tr()), | ||||||
|  |                                 body: Column( | ||||||
|  |                                   spacing: 16, | ||||||
|  |                                   children: [ | ||||||
|  |                                     TextFormField( | ||||||
|  |                                       controller: homePageController, | ||||||
|  |                                       decoration: InputDecoration( | ||||||
|  |                                         labelText: 'homePageUrl'.tr(), | ||||||
|  |                                         hintText: 'https://example.com', | ||||||
|  |                                       ), | ||||||
|  |                                       keyboardType: TextInputType.url, | ||||||
|  |                                     ), | ||||||
|  |                                     TextFormField( | ||||||
|  |                                       controller: privacyPolicyController, | ||||||
|  |                                       decoration: InputDecoration( | ||||||
|  |                                         labelText: 'privacyPolicyUrl'.tr(), | ||||||
|  |                                         hintText: 'https://example.com/privacy', | ||||||
|  |                                       ), | ||||||
|  |                                       keyboardType: TextInputType.url, | ||||||
|  |                                     ), | ||||||
|  |                                     TextFormField( | ||||||
|  |                                       controller: termsController, | ||||||
|  |                                       decoration: InputDecoration( | ||||||
|  |                                         labelText: 'termsOfServiceUrl'.tr(), | ||||||
|  |                                         hintText: 'https://example.com/terms', | ||||||
|  |                                       ), | ||||||
|  |                                       keyboardType: TextInputType.url, | ||||||
|  |                                     ), | ||||||
|  |                                   ], | ||||||
|  |                                 ).padding(horizontal: 16, bottom: 24), | ||||||
|  |                                 isExpanded: enableLinks.value, | ||||||
|  |                               ), | ||||||
|  |                               ExpansionPanel( | ||||||
|  |                                 headerBuilder: | ||||||
|  |                                     (context, isExpanded) => ListTile( | ||||||
|  |                                       title: Text('oauthConfig').tr(), | ||||||
|  |                                     ), | ||||||
|  |                                 body: Column( | ||||||
|  |                                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                                   children: [ | ||||||
|  |                                     Text('redirectUris'.tr()), | ||||||
|  |                                     Card( | ||||||
|  |                                       margin: const EdgeInsets.symmetric( | ||||||
|  |                                         vertical: 8, | ||||||
|  |                                       ), | ||||||
|  |                                       child: Column( | ||||||
|  |                                         children: [ | ||||||
|  |                                           ...redirectUris.value.map( | ||||||
|  |                                             (uri) => ListTile( | ||||||
|  |                                               title: Text(uri), | ||||||
|  |                                               trailing: IconButton( | ||||||
|  |                                                 icon: const Icon( | ||||||
|  |                                                   Symbols.delete, | ||||||
|  |                                                 ), | ||||||
|  |                                                 onPressed: () { | ||||||
|  |                                                   redirectUris.value = | ||||||
|  |                                                       redirectUris.value | ||||||
|  |                                                           .where( | ||||||
|  |                                                             (u) => u != uri, | ||||||
|  |                                                           ) | ||||||
|  |                                                           .toList(); | ||||||
|  |                                                 }, | ||||||
|  |                                               ), | ||||||
|  |                                             ), | ||||||
|  |                                           ), | ||||||
|  |                                           if (redirectUris.value.isNotEmpty) | ||||||
|  |                                             const Divider(height: 1), | ||||||
|  |                                           ListTile( | ||||||
|  |                                             leading: const Icon(Symbols.add), | ||||||
|  |                                             title: Text('addRedirectUri'.tr()), | ||||||
|  |                                             onTap: showAddRedirectUriDialog, | ||||||
|  |                                             shape: RoundedRectangleBorder( | ||||||
|  |                                               borderRadius: | ||||||
|  |                                                   BorderRadius.circular(8), | ||||||
|  |                                             ), | ||||||
|  |                                           ), | ||||||
|  |                                         ], | ||||||
|  |                                       ), | ||||||
|  |                                     ), | ||||||
|  |                                     const SizedBox(height: 16), | ||||||
|  |                                     Text('allowedScopes'.tr()), | ||||||
|  |                                     Card( | ||||||
|  |                                       margin: const EdgeInsets.symmetric( | ||||||
|  |                                         vertical: 8, | ||||||
|  |                                       ), | ||||||
|  |                                       child: Column( | ||||||
|  |                                         children: [ | ||||||
|  |                                           ...allowedScopes.value.map( | ||||||
|  |                                             (scope) => ListTile( | ||||||
|  |                                               title: Text(scope), | ||||||
|  |                                               trailing: IconButton( | ||||||
|  |                                                 icon: const Icon( | ||||||
|  |                                                   Symbols.delete, | ||||||
|  |                                                 ), | ||||||
|  |                                                 onPressed: () { | ||||||
|  |                                                   allowedScopes.value = | ||||||
|  |                                                       allowedScopes.value | ||||||
|  |                                                           .where( | ||||||
|  |                                                             (s) => s != scope, | ||||||
|  |                                                           ) | ||||||
|  |                                                           .toList(); | ||||||
|  |                                                 }, | ||||||
|  |                                               ), | ||||||
|  |                                             ), | ||||||
|  |                                           ), | ||||||
|  |                                           if (allowedScopes.value.isNotEmpty) | ||||||
|  |                                             const Divider(height: 1), | ||||||
|  |                                           ListTile( | ||||||
|  |                                             leading: const Icon(Symbols.add), | ||||||
|  |                                             title: Text('add').tr(), | ||||||
|  |                                             onTap: showAddScopeDialog, | ||||||
|  |                                           ), | ||||||
|  |                                         ], | ||||||
|  |                                       ), | ||||||
|  |                                     ), | ||||||
|  |                                     const SizedBox(height: 16), | ||||||
|  |                                     SwitchListTile( | ||||||
|  |                                       title: Text('requirePkce'.tr()), | ||||||
|  |                                       value: requirePkce.value, | ||||||
|  |                                       onChanged: | ||||||
|  |                                           (value) => requirePkce.value = value, | ||||||
|  |                                     ), | ||||||
|  |                                     SwitchListTile( | ||||||
|  |                                       title: Text('allowOfflineAccess'.tr()), | ||||||
|  |                                       value: allowOfflineAccess.value, | ||||||
|  |                                       onChanged: | ||||||
|  |                                           (value) => | ||||||
|  |                                               allowOfflineAccess.value = value, | ||||||
|  |                                     ), | ||||||
|  |                                   ], | ||||||
|  |                                 ).padding(horizontal: 16, bottom: 24), | ||||||
|  |                                 isExpanded: oauthEnabled.value, | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           Align( | ||||||
|  |                             alignment: Alignment.centerRight, | ||||||
|  |                             child: TextButton.icon( | ||||||
|  |                               onPressed: | ||||||
|  |                                   submitting.value ? null : performAction, | ||||||
|  |                               label: Text('saveChanges'.tr()), | ||||||
|  |                               icon: const Icon(Symbols.save), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ).padding(all: 24), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										161
									
								
								lib/screens/developers/edit_app.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								lib/screens/developers/edit_app.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'edit_app.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$customAppHash() => r'aa4d1fb803c47a99cbacf6d91481f4fce3fda457'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [customApp]. | ||||||
|  | @ProviderFor(customApp) | ||||||
|  | const customAppProvider = CustomAppFamily(); | ||||||
|  |  | ||||||
|  | /// See also [customApp]. | ||||||
|  | class CustomAppFamily extends Family<AsyncValue<CustomApp?>> { | ||||||
|  |   /// See also [customApp]. | ||||||
|  |   const CustomAppFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [customApp]. | ||||||
|  |   CustomAppProvider call(String publisherName, String id) { | ||||||
|  |     return CustomAppProvider(publisherName, id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   CustomAppProvider getProviderOverride(covariant CustomAppProvider provider) { | ||||||
|  |     return call(provider.publisherName, provider.id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'customAppProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [customApp]. | ||||||
|  | class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | ||||||
|  |   /// See also [customApp]. | ||||||
|  |   CustomAppProvider(String publisherName, String id) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => customApp(ref as CustomAppRef, publisherName, id), | ||||||
|  |         from: customAppProvider, | ||||||
|  |         name: r'customAppProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$customAppHash, | ||||||
|  |         dependencies: CustomAppFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: CustomAppFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         id: id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   CustomAppProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.id, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String id; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<CustomApp?> Function(CustomAppRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: CustomAppProvider._internal( | ||||||
|  |         (ref) => create(ref as CustomAppRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         id: id, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<CustomApp?> createElement() { | ||||||
|  |     return _CustomAppProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is CustomAppProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.id == id; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin CustomAppRef on AutoDisposeFutureProviderRef<CustomApp?> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `id` of this provider. | ||||||
|  |   String get id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _CustomAppProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<CustomApp?> | ||||||
|  |     with CustomAppRef { | ||||||
|  |   _CustomAppProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as CustomAppProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get id => (origin as CustomAppProvider).id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
							
								
								
									
										380
									
								
								lib/screens/developers/hub.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								lib/screens/developers/hub.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,380 @@ | |||||||
|  | import 'package:dropdown_button2/dropdown_button2.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/developer.dart'; | ||||||
|  | import 'package:island/models/publisher.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/screens/creators/publishers.dart'; | ||||||
|  | import 'package:island/services/responsive.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'hub.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<DeveloperStats?> developerStats(Ref ref, String? uname) async { | ||||||
|  |   if (uname == null) return null; | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await apiClient.get('/developers/$uname/stats'); | ||||||
|  |   return DeveloperStats.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<SnPublisher>> developers(Ref ref) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/developers'); | ||||||
|  |   return resp.data | ||||||
|  |       .map((e) => SnPublisher.fromJson(e)) | ||||||
|  |       .cast<SnPublisher>() | ||||||
|  |       .toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class DeveloperHubShellScreen extends StatelessWidget { | ||||||
|  |   final Widget child; | ||||||
|  |   const DeveloperHubShellScreen({super.key, required this.child}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     final isWide = isWideScreen(context); | ||||||
|  |     if (isWide) { | ||||||
|  |       return Row( | ||||||
|  |         children: [ | ||||||
|  |           SizedBox(width: 360, child: const DeveloperHubScreen(isAside: true)), | ||||||
|  |           const VerticalDivider(width: 1), | ||||||
|  |           Expanded(child: child), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     return child; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class DeveloperHubScreen extends HookConsumerWidget { | ||||||
|  |   final bool isAside; | ||||||
|  |   const DeveloperHubScreen({super.key, this.isAside = false}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final isWide = isWideScreen(context); | ||||||
|  |     if (isWide && !isAside) { | ||||||
|  |       return Container(color: Theme.of(context).colorScheme.surface); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final developers = ref.watch(developersProvider); | ||||||
|  |     final currentDeveloper = useState<SnPublisher?>( | ||||||
|  |       developers.value?.firstOrNull, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final List<DropdownMenuItem<SnPublisher>> developersMenu = developers.when( | ||||||
|  |       data: | ||||||
|  |           (data) => | ||||||
|  |               data | ||||||
|  |                   .map( | ||||||
|  |                     (item) => DropdownMenuItem<SnPublisher>( | ||||||
|  |                       value: item, | ||||||
|  |                       child: ListTile( | ||||||
|  |                         minTileHeight: 48, | ||||||
|  |                         leading: ProfilePictureWidget( | ||||||
|  |                           radius: 16, | ||||||
|  |                           fileId: item.picture?.id, | ||||||
|  |                         ), | ||||||
|  |                         title: Text(item.nick), | ||||||
|  |                         subtitle: Text('@${item.name}'), | ||||||
|  |                         trailing: | ||||||
|  |                             currentDeveloper.value?.id == item.id | ||||||
|  |                                 ? const Icon(Icons.check) | ||||||
|  |                                 : null, | ||||||
|  |                         contentPadding: EdgeInsets.symmetric(horizontal: 8), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ) | ||||||
|  |                   .toList(), | ||||||
|  |       loading: () => [], | ||||||
|  |       error: (_, _) => [], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final developerStats = ref.watch( | ||||||
|  |       developerStatsProvider(currentDeveloper.value?.name), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       noBackground: false, | ||||||
|  |       appBar: AppBar( | ||||||
|  |         leading: !isWide ? const PageBackButton() : null, | ||||||
|  |         title: Text('developerHub').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           DropdownButtonHideUnderline( | ||||||
|  |             child: DropdownButton2<SnPublisher>( | ||||||
|  |               alignment: Alignment.centerRight, | ||||||
|  |               value: currentDeveloper.value, | ||||||
|  |               hint: CircleAvatar( | ||||||
|  |                 radius: 16, | ||||||
|  |                 child: Icon( | ||||||
|  |                   Symbols.person, | ||||||
|  |                   color: Theme.of( | ||||||
|  |                     context, | ||||||
|  |                   ).colorScheme.onSecondaryContainer.withOpacity(0.9), | ||||||
|  |                   fill: 1, | ||||||
|  |                 ), | ||||||
|  |               ).center().padding(right: 8), | ||||||
|  |               items: [...developersMenu], | ||||||
|  |               onChanged: (value) { | ||||||
|  |                 currentDeveloper.value = value; | ||||||
|  |               }, | ||||||
|  |               selectedItemBuilder: (context) { | ||||||
|  |                 return [ | ||||||
|  |                   ...developersMenu.map( | ||||||
|  |                     (e) => ProfilePictureWidget( | ||||||
|  |                       radius: 16, | ||||||
|  |                       fileId: e.value?.picture?.id, | ||||||
|  |                     ).center().padding(right: 8), | ||||||
|  |                   ), | ||||||
|  |                 ]; | ||||||
|  |               }, | ||||||
|  |               buttonStyleData: ButtonStyleData( | ||||||
|  |                 height: 40, | ||||||
|  |                 padding: const EdgeInsets.only(left: 14, right: 8), | ||||||
|  |                 decoration: BoxDecoration( | ||||||
|  |                   borderRadius: BorderRadius.circular(20), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               dropdownStyleData: DropdownStyleData( | ||||||
|  |                 width: 320, | ||||||
|  |                 padding: const EdgeInsets.symmetric(vertical: 6), | ||||||
|  |                 decoration: BoxDecoration( | ||||||
|  |                   borderRadius: BorderRadius.circular(4), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               menuItemStyleData: const MenuItemStyleData( | ||||||
|  |                 height: 64, | ||||||
|  |                 padding: EdgeInsets.only(left: 14, right: 14), | ||||||
|  |               ), | ||||||
|  |               iconStyleData: IconStyleData( | ||||||
|  |                 icon: Icon(Icons.arrow_drop_down), | ||||||
|  |                 iconSize: 19, | ||||||
|  |                 iconEnabledColor: | ||||||
|  |                     Theme.of(context).appBarTheme.foregroundColor!, | ||||||
|  |                 iconDisabledColor: | ||||||
|  |                     Theme.of(context).appBarTheme.foregroundColor!, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           const Gap(8), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       body: developerStats.when( | ||||||
|  |         data: | ||||||
|  |             (stats) => SingleChildScrollView( | ||||||
|  |               child: | ||||||
|  |                   currentDeveloper.value == null | ||||||
|  |                       ? Column( | ||||||
|  |                         children: [ | ||||||
|  |                           const Gap(24), | ||||||
|  |                           const Icon(Symbols.info, size: 32).padding(bottom: 4), | ||||||
|  |                           Text( | ||||||
|  |                             'developerHubUnselectedHint', | ||||||
|  |                             textAlign: TextAlign.center, | ||||||
|  |                           ).tr(), | ||||||
|  |                           const Gap(24), | ||||||
|  |                           const Divider(height: 1), | ||||||
|  |                           ...(developers.value?.map( | ||||||
|  |                                 (developer) => ListTile( | ||||||
|  |                                   leading: ProfilePictureWidget( | ||||||
|  |                                     file: developer.picture, | ||||||
|  |                                   ), | ||||||
|  |                                   title: Text(developer.nick), | ||||||
|  |                                   subtitle: Text('@${developer.name}'), | ||||||
|  |                                   onTap: () { | ||||||
|  |                                     currentDeveloper.value = developer; | ||||||
|  |                                   }, | ||||||
|  |                                 ), | ||||||
|  |                               ) ?? | ||||||
|  |                               []), | ||||||
|  |                           ListTile( | ||||||
|  |                             leading: const CircleAvatar( | ||||||
|  |                               child: Icon(Symbols.add), | ||||||
|  |                             ), | ||||||
|  |                             title: Text('enrollDeveloper').tr(), | ||||||
|  |                             subtitle: Text('enrollDeveloperHint').tr(), | ||||||
|  |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                             onTap: () { | ||||||
|  |                               showModalBottomSheet( | ||||||
|  |                                 context: context, | ||||||
|  |                                 isScrollControlled: true, | ||||||
|  |                                 builder: | ||||||
|  |                                     (_) => const _DeveloperEnrollmentSheet(), | ||||||
|  |                               ).then((value) { | ||||||
|  |                                 if (value == true) { | ||||||
|  |                                   ref.invalidate(developersProvider); | ||||||
|  |                                 } | ||||||
|  |                               }); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ) | ||||||
|  |                       : Column( | ||||||
|  |                         children: [ | ||||||
|  |                           if (stats != null) | ||||||
|  |                             _DeveloperStatsWidget( | ||||||
|  |                               stats: stats, | ||||||
|  |                             ).padding(vertical: 12, horizontal: 12), | ||||||
|  |                           ListTile( | ||||||
|  |                             minTileHeight: 48, | ||||||
|  |                             title: Text('customApps').tr(), | ||||||
|  |                             trailing: Icon(Symbols.chevron_right), | ||||||
|  |                             leading: const Icon(Symbols.apps), | ||||||
|  |                             contentPadding: EdgeInsets.symmetric( | ||||||
|  |                               horizontal: 24, | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.push( | ||||||
|  |                           '/developers/${currentDeveloper.value!.name}/apps', | ||||||
|  |                         ); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |             ), | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (err, stack) => ResponseErrorWidget( | ||||||
|  |               error: err, | ||||||
|  |               onRetry: () { | ||||||
|  |                 ref.invalidate( | ||||||
|  |                   developerStatsProvider(currentDeveloper.value?.name), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _DeveloperStatsWidget extends StatelessWidget { | ||||||
|  |   final DeveloperStats stats; | ||||||
|  |   const _DeveloperStatsWidget({required this.stats}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return SingleChildScrollView( | ||||||
|  |       child: Column( | ||||||
|  |         spacing: 8, | ||||||
|  |         children: [ | ||||||
|  |           Row( | ||||||
|  |             spacing: 8, | ||||||
|  |             children: [ | ||||||
|  |               Expanded( | ||||||
|  |                 child: _buildStatsCard( | ||||||
|  |                   context, | ||||||
|  |                   stats.totalCustomApps.toString(), | ||||||
|  |                   'totalCustomApps', | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget _buildStatsCard( | ||||||
|  |     BuildContext context, | ||||||
|  |     String statValue, | ||||||
|  |     String statLabel, | ||||||
|  |   ) { | ||||||
|  |     return Card( | ||||||
|  |       margin: EdgeInsets.zero, | ||||||
|  |       child: SizedBox( | ||||||
|  |         height: 100, | ||||||
|  |         child: Padding( | ||||||
|  |           padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |             mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |             children: [ | ||||||
|  |               Text( | ||||||
|  |                 statValue, | ||||||
|  |                 style: Theme.of(context).textTheme.headlineMedium, | ||||||
|  |               ), | ||||||
|  |               const Gap(4), | ||||||
|  |               Text( | ||||||
|  |                 statLabel, | ||||||
|  |                 maxLines: 1, | ||||||
|  |                 overflow: TextOverflow.ellipsis, | ||||||
|  |               ).tr(), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _DeveloperEnrollmentSheet extends HookConsumerWidget { | ||||||
|  |   const _DeveloperEnrollmentSheet(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final publishers = ref.watch(publishersManagedProvider); | ||||||
|  |  | ||||||
|  |     Future<void> enroll(SnPublisher publisher) async { | ||||||
|  |       try { | ||||||
|  |         final client = ref.read(apiClientProvider); | ||||||
|  |         await client.post('/developers/${publisher.name}/enroll'); | ||||||
|  |         if (context.mounted) { | ||||||
|  |           Navigator.pop(context, true); | ||||||
|  |         } | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return SheetScaffold( | ||||||
|  |       titleText: 'enrollDeveloper'.tr(), | ||||||
|  |       child: publishers.when( | ||||||
|  |         data: | ||||||
|  |             (items) => | ||||||
|  |                 items.isEmpty | ||||||
|  |                     ? Center( | ||||||
|  |                       child: | ||||||
|  |                           Text( | ||||||
|  |                             'noPublishersToEnroll', | ||||||
|  |                             textAlign: TextAlign.center, | ||||||
|  |                           ).tr(), | ||||||
|  |                     ) | ||||||
|  |                     : ListView.builder( | ||||||
|  |                       shrinkWrap: true, | ||||||
|  |                       itemCount: items.length, | ||||||
|  |                       itemBuilder: (context, index) { | ||||||
|  |                         final publisher = items[index]; | ||||||
|  |                         return ListTile( | ||||||
|  |                           leading: ProfilePictureWidget( | ||||||
|  |                             fileId: publisher.picture?.id, | ||||||
|  |                             fallbackIcon: Symbols.group, | ||||||
|  |                           ), | ||||||
|  |                           title: Text(publisher.nick), | ||||||
|  |                           subtitle: Text('@${publisher.name}'), | ||||||
|  |                           onTap: () => enroll(publisher), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, _) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(publishersManagedProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										172
									
								
								lib/screens/developers/hub.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								lib/screens/developers/hub.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'hub.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$developerStatsHash() => r'783398cbde09c3d956c3e20b02a1cebd1f8ab748'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [developerStats]. | ||||||
|  | @ProviderFor(developerStats) | ||||||
|  | const developerStatsProvider = DeveloperStatsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [developerStats]. | ||||||
|  | class DeveloperStatsFamily extends Family<AsyncValue<DeveloperStats?>> { | ||||||
|  |   /// See also [developerStats]. | ||||||
|  |   const DeveloperStatsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [developerStats]. | ||||||
|  |   DeveloperStatsProvider call(String? uname) { | ||||||
|  |     return DeveloperStatsProvider(uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   DeveloperStatsProvider getProviderOverride( | ||||||
|  |     covariant DeveloperStatsProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'developerStatsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [developerStats]. | ||||||
|  | class DeveloperStatsProvider | ||||||
|  |     extends AutoDisposeFutureProvider<DeveloperStats?> { | ||||||
|  |   /// See also [developerStats]. | ||||||
|  |   DeveloperStatsProvider(String? uname) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => developerStats(ref as DeveloperStatsRef, uname), | ||||||
|  |         from: developerStatsProvider, | ||||||
|  |         name: r'developerStatsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$developerStatsHash, | ||||||
|  |         dependencies: DeveloperStatsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             DeveloperStatsFamily._allTransitiveDependencies, | ||||||
|  |         uname: uname, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   DeveloperStatsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.uname, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String? uname; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<DeveloperStats?> Function(DeveloperStatsRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: DeveloperStatsProvider._internal( | ||||||
|  |         (ref) => create(ref as DeveloperStatsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         uname: uname, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<DeveloperStats?> createElement() { | ||||||
|  |     return _DeveloperStatsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is DeveloperStatsProvider && other.uname == uname; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, uname.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin DeveloperStatsRef on AutoDisposeFutureProviderRef<DeveloperStats?> { | ||||||
|  |   /// The parameter `uname` of this provider. | ||||||
|  |   String? get uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _DeveloperStatsProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<DeveloperStats?> | ||||||
|  |     with DeveloperStatsRef { | ||||||
|  |   _DeveloperStatsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get uname => (origin as DeveloperStatsProvider).uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$developersHash() => r'f52639d3c21aafbf235c8ae33f35448baf2989a1'; | ||||||
|  |  | ||||||
|  | /// See also [developers]. | ||||||
|  | @ProviderFor(developers) | ||||||
|  | final developersProvider = | ||||||
|  |     AutoDisposeFutureProvider<List<SnPublisher>>.internal( | ||||||
|  |       developers, | ||||||
|  |       name: r'developersProvider', | ||||||
|  |       debugGetCreateSourceHash: | ||||||
|  |           const bool.fromEnvironment('dart.vm.product') | ||||||
|  |               ? null | ||||||
|  |               : _$developersHash, | ||||||
|  |       dependencies: null, | ||||||
|  |       allTransitiveDependencies: null, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnPublisher>>; | ||||||
|  | // 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 | ||||||
							
								
								
									
										12
									
								
								lib/screens/developers/new_app.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								lib/screens/developers/new_app.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_app.dart'; | ||||||
|  |  | ||||||
|  | class NewCustomAppScreen extends StatelessWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   const NewCustomAppScreen({super.key, required this.publisherName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return EditAppScreen(publisherName: publisherName); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -6,6 +6,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/publisher.dart'; | ||||||
| import 'package:island/models/realm.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'; | ||||||
|   | |||||||
| @@ -4,19 +4,18 @@ import 'dart:math' as math; | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:flutter/services.dart'; |  | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/user.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/pods/websocket.dart'; | import 'package:island/pods/websocket.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/route.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; | import 'package:island/widgets/app_scaffold.dart'; | ||||||
| import 'package:island/widgets/content/markdown.dart'; | import 'package:island/widgets/content/markdown.dart'; | ||||||
| import 'package:relative_time/relative_time.dart'; | import 'package:relative_time/relative_time.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:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| import 'package:url_launcher/url_launcher.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
| part 'notification.g.dart'; | part 'notification.g.dart'; | ||||||
|  |  | ||||||
| @@ -180,36 +179,17 @@ class NotificationScreen extends HookConsumerWidget { | |||||||
|                             ), |                             ), | ||||||
|                           ), |                           ), | ||||||
|                   onTap: () { |                   onTap: () { | ||||||
|                     if (notification.meta['link'] is String) { |                     if (notification.meta['action_uri'] != null) { | ||||||
|                       final href = notification.meta['link']; |                       var uri = notification.meta['action_uri'] as String; | ||||||
|                       final uri = Uri.tryParse(href); |                       if (uri.startsWith('/')) { | ||||||
|                       if (uri == null) { |                         // In-app routes | ||||||
|                         showSnackBar( |                         rootNavigatorKey.currentContext?.push( | ||||||
|                           'brokenLink'.tr(args: []), |                           notification.meta['action_uri'], | ||||||
|                           action: SnackBarAction( |  | ||||||
|                             label: 'copyToClipboard'.tr(), |  | ||||||
|                             onPressed: () { |  | ||||||
|                               Clipboard.setData(ClipboardData(text: href)); |  | ||||||
|                               clearSnackBar(context); |  | ||||||
|                             }, |  | ||||||
|                           ), |  | ||||||
|                         ); |                         ); | ||||||
|                         return; |                       } else { | ||||||
|  |                         // External URLs | ||||||
|  |                         launchUrlString(uri); | ||||||
|                       } |                       } | ||||||
|                       if (uri.scheme == 'solian') { |  | ||||||
|                         context.push( |  | ||||||
|                           ['', uri.host, ...uri.pathSegments].join('/'), |  | ||||||
|                         ); |  | ||||||
|                         return; |  | ||||||
|                       } |  | ||||||
|                       showConfirmAlert( |  | ||||||
|                         'openLinkConfirmDescription'.tr(args: [href]), |  | ||||||
|                         'openLinkConfirm'.tr(), |  | ||||||
|                       ).then((value) { |  | ||||||
|                         if (value) { |  | ||||||
|                           launchUrl(uri, mode: LaunchMode.externalApplication); |  | ||||||
|                         } |  | ||||||
|                       }); |  | ||||||
|                     } |                     } | ||||||
|                   }, |                   }, | ||||||
|                 ); |                 ); | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ import 'package:island/widgets/content/cloud_files.dart'; | |||||||
| import 'package:island/widgets/post/compose_shared.dart'; | import 'package:island/widgets/post/compose_shared.dart'; | ||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.dart'; | ||||||
| import 'package:island/widgets/post/publishers_modal.dart'; | import 'package:island/widgets/post/publishers_modal.dart'; | ||||||
| import 'package:island/screens/posts/detail.dart'; | import 'package:island/screens/posts/post_detail.dart'; | ||||||
| import 'package:island/widgets/post/compose_settings_sheet.dart'; | import 'package:island/widgets/post/compose_settings_sheet.dart'; | ||||||
| import 'package:island/services/compose_storage_db.dart'; | import 'package:island/services/compose_storage_db.dart'; | ||||||
| import 'package:island/widgets/post/draft_manager.dart'; | import 'package:island/widgets/post/draft_manager.dart'; | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import 'package:island/models/post.dart'; | |||||||
| import 'package:island/screens/creators/publishers.dart'; | import 'package:island/screens/creators/publishers.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'; | ||||||
| import 'package:island/screens/posts/detail.dart'; | import 'package:island/screens/posts/post_detail.dart'; | ||||||
| import 'package:island/widgets/content/attachment_preview.dart'; | import 'package:island/widgets/content/attachment_preview.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/content/markdown.dart'; | import 'package:island/widgets/content/markdown.dart'; | ||||||
|   | |||||||
| @@ -9,10 +9,11 @@ import 'package:island/widgets/app_scaffold.dart'; | |||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.dart'; | ||||||
| import 'package:island/widgets/post/post_quick_reply.dart'; | import 'package:island/widgets/post/post_quick_reply.dart'; | ||||||
| import 'package:island/widgets/post/post_replies.dart'; | import 'package:island/widgets/post/post_replies.dart'; | ||||||
|  | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
| import 'package:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| 
 | 
 | ||||||
| part 'detail.g.dart'; | part 'post_detail.g.dart'; | ||||||
| 
 | 
 | ||||||
| @riverpod | @riverpod | ||||||
| Future<SnPost?> post(Ref ref, String id) async { | Future<SnPost?> post(Ref ref, String id) async { | ||||||
| @@ -21,20 +22,43 @@ Future<SnPost?> post(Ref ref, String id) async { | |||||||
|   return SnPost.fromJson(resp.data); |   return SnPost.fromJson(resp.data); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | final postStateProvider = StateNotifierProvider.family<PostState, AsyncValue<SnPost?>, String>( | ||||||
|  |   (ref, id) => PostState(ref, id), | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | class PostState extends StateNotifier<AsyncValue<SnPost?>> { | ||||||
|  |   final Ref _ref; | ||||||
|  |   final String _id; | ||||||
|  | 
 | ||||||
|  |   PostState(this._ref, this._id) : super(const AsyncValue.loading()) { | ||||||
|  |     // Initialize with the initial post data | ||||||
|  |     _ref.listen<AsyncValue<SnPost?>>( | ||||||
|  |       postProvider(_id), | ||||||
|  |       (_, next) => state = next, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   void updatePost(SnPost? newPost) { | ||||||
|  |     if (newPost != null) { | ||||||
|  |       state = AsyncData(newPost); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class PostDetailScreen extends HookConsumerWidget { | class PostDetailScreen extends HookConsumerWidget { | ||||||
|   final String id; |   final String id; | ||||||
|   const PostDetailScreen({super.key, required this.id}); |   const PostDetailScreen({super.key, required this.id}); | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final post = ref.watch(postProvider(id)); |     final postState = ref.watch(postStateProvider(id)); | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
| 
 | 
 | ||||||
|     final isWide = isWideScreen(context); |     final isWide = isWideScreen(context); | ||||||
| 
 | 
 | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar(title: const Text('Post')), |       appBar: AppBar(title: const Text('Post')), | ||||||
|       body: post.when( |       body: postState.when( | ||||||
|         data: (post) { |         data: (post) { | ||||||
|           return Stack( |           return Stack( | ||||||
|             fit: StackFit.expand, |             fit: StackFit.expand, | ||||||
| @@ -49,6 +73,10 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|                           isOpenable: false, |                           isOpenable: false, | ||||||
|                           isFullPost: true, |                           isFullPost: true, | ||||||
|                           backgroundColor: isWide ? Colors.transparent : null, |                           backgroundColor: isWide ? Colors.transparent : null, | ||||||
|  |                           onUpdate: (newItem) { | ||||||
|  |                             // Update the local state with the new post data | ||||||
|  |                             ref.read(postStateProvider(id).notifier).updatePost(newItem); | ||||||
|  |                           }, | ||||||
|                         ), |                         ), | ||||||
|                         const Divider(height: 1), |                         const Divider(height: 1), | ||||||
|                       ], |                       ], | ||||||
| @@ -65,11 +93,15 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|                   right: 0, |                   right: 0, | ||||||
|                   child: Material( |                   child: Material( | ||||||
|                     elevation: 2, |                     elevation: 2, | ||||||
|                     child: PostQuickReply( |                     child: postState.when( | ||||||
|                       parent: post, |                       data: (post) => PostQuickReply( | ||||||
|                       onPosted: () { |                         parent: post!, | ||||||
|                         ref.invalidate(postRepliesNotifierProvider(id)); |                         onPosted: () { | ||||||
|                       }, |                           ref.invalidate(postRepliesNotifierProvider(id)); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                       loading: () => const SizedBox.shrink(), | ||||||
|  |                       error: (_, __) => const SizedBox.shrink(), | ||||||
|                     ).padding( |                     ).padding( | ||||||
|                       bottom: MediaQuery.of(context).padding.bottom + 16, |                       bottom: MediaQuery.of(context).padding.bottom + 16, | ||||||
|                       top: 16, |                       top: 16, | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
| 
 | 
 | ||||||
| part of 'detail.dart'; | part of 'post_detail.dart'; | ||||||
| 
 | 
 | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| @@ -6,6 +6,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/post.dart'; | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/models/publisher.dart'; | ||||||
| import 'package:island/models/user.dart'; | import 'package:island/models/user.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
|   | |||||||
| @@ -400,7 +400,7 @@ class _PublisherSubscriptionStatusProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$publisherAppbarForcegroundColorHash() => | String _$publisherAppbarForcegroundColorHash() => | ||||||
|     r'3ff2eebb48d3f3af1907052f471e648f5b14b13c'; |     r'd781a806a242aea5c1609ec98c97c52fdd9f7db1'; | ||||||
|  |  | ||||||
| /// See also [publisherAppbarForcegroundColor]. | /// See also [publisherAppbarForcegroundColor]. | ||||||
| @ProviderFor(publisherAppbarForcegroundColor) | @ProviderFor(publisherAppbarForcegroundColor) | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ 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:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
| 
 | 
 | ||||||
| part 'detail.g.dart'; | part 'realm_detail.g.dart'; | ||||||
| 
 | 
 | ||||||
| @riverpod | @riverpod | ||||||
| Future<Color?> realmAppbarForegroundColor(Ref ref, String realmSlug) async { | Future<Color?> realmAppbarForegroundColor(Ref ref, String realmSlug) async { | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
| 
 | 
 | ||||||
| part of 'detail.dart'; | part of 'realm_detail.dart'; | ||||||
| 
 | 
 | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| @@ -294,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: () { | ||||||
| @@ -344,24 +344,46 @@ class EditRealmScreen extends HookConsumerWidget { | |||||||
|                 const SizedBox(height: 16), |                 const SizedBox(height: 16), | ||||||
|                 TextFormField( |                 TextFormField( | ||||||
|                   controller: descriptionController, |                   controller: descriptionController, | ||||||
|                   decoration: InputDecoration(labelText: 'description'.tr()), |                   decoration: InputDecoration( | ||||||
|  |                     labelText: 'description'.tr(), | ||||||
|  |                     alignLabelWithHint: true, | ||||||
|  |                   ), | ||||||
|                   minLines: 3, |                   minLines: 3, | ||||||
|                   maxLines: null, |                   maxLines: null, | ||||||
|                   onTapOutside: |                   onTapOutside: | ||||||
|                       (_) => FocusManager.instance.primaryFocus?.unfocus(), |                       (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|                 ), |                 ), | ||||||
|                 const SizedBox(height: 16), |                 const SizedBox(height: 16), | ||||||
|                 CheckboxListTile( |                 Card( | ||||||
|                   title: const Text('isPublic').tr(), |                   margin: EdgeInsets.zero, | ||||||
|                   subtitle: const Text('isPublicHint').tr(), |                   child: Column( | ||||||
|                   value: isPublic.value, |                     children: [ | ||||||
|                   onChanged: (value) => isPublic.value = value ?? false, |                       CheckboxListTile( | ||||||
|                 ), |                         secondary: const Icon(Symbols.public), | ||||||
|                 CheckboxListTile( |                         title: Text('publicRealm').tr(), | ||||||
|                   title: const Text('isCommunity').tr(), |                         subtitle: Text('publicRealmDescription').tr(), | ||||||
|                   subtitle: const Text('isCommunityHint').tr(), |                         value: isPublic.value, | ||||||
|                   value: isCommunity.value, |                         onChanged: (value) { | ||||||
|                   onChanged: (value) => isCommunity.value = value ?? false, |                           isPublic.value = value ?? true; | ||||||
|  |                         }, | ||||||
|  |                         shape: RoundedRectangleBorder( | ||||||
|  |                           borderRadius: BorderRadius.circular(8), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                       CheckboxListTile( | ||||||
|  |                         secondary: const Icon(Symbols.travel_explore), | ||||||
|  |                         title: Text('communityRealm').tr(), | ||||||
|  |                         subtitle: Text('communityRealmDescription').tr(), | ||||||
|  |                         value: isCommunity.value, | ||||||
|  |                         onChanged: (value) { | ||||||
|  |                           isCommunity.value = value ?? false; | ||||||
|  |                         }, | ||||||
|  |                         shape: RoundedRectangleBorder( | ||||||
|  |                           borderRadius: BorderRadius.circular(8), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|                 ), |                 ), | ||||||
|                 const SizedBox(height: 16), |                 const SizedBox(height: 16), | ||||||
|                 Align( |                 Align( | ||||||
| @@ -435,47 +457,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( | ||||||
| @@ -485,4 +507,4 @@ class _RealmInviteSheet extends HookConsumerWidget { | |||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -341,6 +341,25 @@ class SettingsScreen extends HookConsumerWidget { | |||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     final behaviorSettings = [ |     final behaviorSettings = [ | ||||||
|  |       ListTile( | ||||||
|  |         minLeadingWidth: 48, | ||||||
|  |         title: Text('creatorHub').tr(), | ||||||
|  |         contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |         leading: const Icon(Symbols.rocket_launch), | ||||||
|  |         trailing: const Icon(Symbols.chevron_right), | ||||||
|  |         onTap: () => context.push('/creators'), | ||||||
|  |       ), | ||||||
|  |  | ||||||
|  |       // Developer Hub | ||||||
|  |       ListTile( | ||||||
|  |         minLeadingWidth: 48, | ||||||
|  |         title: Text('developerHub').tr(), | ||||||
|  |         contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||||
|  |         leading: const Icon(Symbols.hub), | ||||||
|  |         trailing: const Icon(Symbols.chevron_right), | ||||||
|  |         onTap: () => context.push('/developers'), | ||||||
|  |       ), | ||||||
|  |  | ||||||
|       // Auto translate settings |       // Auto translate settings | ||||||
|       ListTile( |       ListTile( | ||||||
|         minLeadingWidth: 48, |         minLeadingWidth: 48, | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ class AudioCallButton extends HookConsumerWidget { | |||||||
|       try { |       try { | ||||||
|         await apiClient.post('/chat/realtime/$roomId'); |         await apiClient.post('/chat/realtime/$roomId'); | ||||||
|         if (context.mounted) { |         if (context.mounted) { | ||||||
|           context.push('/chat/call/roomId'); |           context.push('/chat/call/$roomId'); | ||||||
|         } |         } | ||||||
|       } catch (e) { |       } catch (e) { | ||||||
|         showErrorAlert(e); |         showErrorAlert(e); | ||||||
| @@ -96,7 +96,7 @@ class AudioCallButton extends HookConsumerWidget { | |||||||
|         tooltip: 'Join Ongoing Call', |         tooltip: 'Join Ongoing Call', | ||||||
|         onPressed: () { |         onPressed: () { | ||||||
|           if (context.mounted) { |           if (context.mounted) { | ||||||
|             context.push('/chat/call/roomId'); |             context.push('/chat/$roomId/call'); | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -360,7 +360,7 @@ class CallOverlayBar extends HookConsumerWidget { | |||||||
|         ).padding(all: 16), |         ).padding(all: 16), | ||||||
|       ), |       ), | ||||||
|       onTap: () { |       onTap: () { | ||||||
|         context.push('/chat/call/callNotifier.roomId!'); |         context.push('/chat/call/${callNotifier.roomId!}'); | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import 'package:island/pods/config.dart'; | |||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
| import 'package:path/path.dart' show extension; | import 'package:path/path.dart' show extension; | ||||||
| import 'package:path_provider/path_provider.dart'; | import 'package:path_provider/path_provider.dart'; | ||||||
| import 'package:photo_view/photo_view.dart'; | import 'package:photo_view/photo_view.dart'; | ||||||
| @@ -125,6 +126,7 @@ class CloudFileList extends HookConsumerWidget { | |||||||
|               if (!disableZoomIn) { |               if (!disableZoomIn) { | ||||||
|                 context.pushTransparentRoute( |                 context.pushTransparentRoute( | ||||||
|                   CloudFileZoomIn(item: files[i], heroTag: heroTags[i]), |                   CloudFileZoomIn(item: files[i], heroTag: heroTags[i]), | ||||||
|  |                   rootNavigator: true, | ||||||
|                 ); |                 ); | ||||||
|               } |               } | ||||||
|             }, |             }, | ||||||
| @@ -209,6 +211,124 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     Widget buildInfoRow(IconData icon, String label, String value) { | ||||||
|  |       return Padding( | ||||||
|  |         padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), | ||||||
|  |         child: Row( | ||||||
|  |           children: [ | ||||||
|  |             Icon( | ||||||
|  |               icon, | ||||||
|  |               size: 20, | ||||||
|  |               color: Theme.of(context).colorScheme.onSurface, | ||||||
|  |             ), | ||||||
|  |             const SizedBox(width: 12), | ||||||
|  |             Text( | ||||||
|  |               label, | ||||||
|  |               style: Theme.of(context).textTheme.bodyMedium?.copyWith( | ||||||
|  |                 color: Theme.of(context).textTheme.bodySmall?.color, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const Spacer(), | ||||||
|  |             Flexible( | ||||||
|  |               child: Text( | ||||||
|  |                 value, | ||||||
|  |                 style: Theme.of( | ||||||
|  |                   context, | ||||||
|  |                 ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), | ||||||
|  |                 textAlign: TextAlign.end, | ||||||
|  |                 overflow: TextOverflow.ellipsis, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     String _formatFileSize(int bytes) { | ||||||
|  |       if (bytes <= 0) return '0 B'; | ||||||
|  |       if (bytes < 1024) return '$bytes B'; | ||||||
|  |       if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; | ||||||
|  |       if (bytes < 1024 * 1024 * 1024) { | ||||||
|  |         return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; | ||||||
|  |       } | ||||||
|  |       return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void showInfoSheet() { | ||||||
|  |       final theme = Theme.of(context); | ||||||
|  |       final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {}; | ||||||
|  |  | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         useRootNavigator: true, | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: | ||||||
|  |             (context) => SheetScaffold( | ||||||
|  |               titleText: 'File Information', | ||||||
|  |               child: SingleChildScrollView( | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                   children: [ | ||||||
|  |                     buildInfoRow(Icons.description, 'Name', item.name), | ||||||
|  |                     const Divider(height: 1), | ||||||
|  |                     buildInfoRow( | ||||||
|  |                       Icons.storage, | ||||||
|  |                       'Size', | ||||||
|  |                       _formatFileSize(item.size), | ||||||
|  |                     ), | ||||||
|  |                     const Divider(height: 1), | ||||||
|  |                     buildInfoRow( | ||||||
|  |                       Icons.category, | ||||||
|  |                       'Type', | ||||||
|  |                       item.mimeType?.toUpperCase() ?? 'UNKNOWN', | ||||||
|  |                     ), | ||||||
|  |                     if (exifData.isNotEmpty) ...[ | ||||||
|  |                       const SizedBox(height: 16), | ||||||
|  |                       Text( | ||||||
|  |                         'EXIF Data', | ||||||
|  |                         style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                         ), | ||||||
|  |                       ).padding(horizontal: 24), | ||||||
|  |                       const SizedBox(height: 8), | ||||||
|  |                       Column( | ||||||
|  |                         crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                         children: [ | ||||||
|  |                           ...exifData.entries.map( | ||||||
|  |                             (entry) => Padding( | ||||||
|  |                               padding: const EdgeInsets.symmetric(vertical: 4), | ||||||
|  |                               child: Row( | ||||||
|  |                                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                                 children: [ | ||||||
|  |                                   Text( | ||||||
|  |                                     '• ${entry.key.contains('-') ? entry.key.split('-').last : entry.key}: ', | ||||||
|  |                                     style: theme.textTheme.bodyMedium?.copyWith( | ||||||
|  |                                       fontWeight: FontWeight.w500, | ||||||
|  |                                     ), | ||||||
|  |                                   ), | ||||||
|  |                                   Expanded( | ||||||
|  |                                     child: Text( | ||||||
|  |                                       '${entry.value}'.isNotEmpty | ||||||
|  |                                           ? '${entry.value}' | ||||||
|  |                                           : 'N/A', | ||||||
|  |                                       style: theme.textTheme.bodyMedium, | ||||||
|  |                                     ), | ||||||
|  |                                   ), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ).padding(horizontal: 24), | ||||||
|  |                     ], | ||||||
|  |                     const SizedBox(height: 16), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return DismissiblePage( |     return DismissiblePage( | ||||||
|       isFullScreen: true, |       isFullScreen: true, | ||||||
|       backgroundColor: Colors.transparent, |       backgroundColor: Colors.transparent, | ||||||
| @@ -287,8 +407,22 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|             left: 16, |             left: 16, | ||||||
|             right: 16, |             right: 16, | ||||||
|             child: Row( |             child: Row( | ||||||
|               mainAxisAlignment: MainAxisAlignment.end, |  | ||||||
|               children: [ |               children: [ | ||||||
|  |                 IconButton( | ||||||
|  |                   icon: Icon( | ||||||
|  |                     Icons.info_outline, | ||||||
|  |                     color: Colors.white, | ||||||
|  |                     shadows: [ | ||||||
|  |                       Shadow( | ||||||
|  |                         color: Colors.black54, | ||||||
|  |                         blurRadius: 5.0, | ||||||
|  |                         offset: Offset(1.0, 1.0), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                   onPressed: showInfoSheet, | ||||||
|  |                 ), | ||||||
|  |                 Spacer(), | ||||||
|                 IconButton( |                 IconButton( | ||||||
|                   icon: Icon(Icons.remove, color: Colors.white), |                   icon: Icon(Icons.remove, color: Colors.white), | ||||||
|                   onPressed: () { |                   onPressed: () { | ||||||
|   | |||||||
							
								
								
									
										51
									
								
								lib/widgets/empty_state.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								lib/widgets/empty_state.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
|  | class EmptyState extends StatelessWidget { | ||||||
|  |   final IconData icon; | ||||||
|  |   final String title; | ||||||
|  |   final String description; | ||||||
|  |   final Widget? action; | ||||||
|  |  | ||||||
|  |   const EmptyState({ | ||||||
|  |     super.key, | ||||||
|  |     required this.icon, | ||||||
|  |     required this.title, | ||||||
|  |     required this.description, | ||||||
|  |     this.action, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Center( | ||||||
|  |       child: Padding( | ||||||
|  |         padding: const EdgeInsets.all(24.0), | ||||||
|  |         child: Column( | ||||||
|  |           mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             Icon( | ||||||
|  |               icon, | ||||||
|  |               size: 64, | ||||||
|  |               color: Theme.of(context).colorScheme.outline, | ||||||
|  |             ), | ||||||
|  |             const SizedBox(height: 16), | ||||||
|  |             Text( | ||||||
|  |               title, | ||||||
|  |               style: Theme.of(context).textTheme.titleLarge, | ||||||
|  |               textAlign: TextAlign.center, | ||||||
|  |             ), | ||||||
|  |             const SizedBox(height: 8), | ||||||
|  |             Text( | ||||||
|  |               description, | ||||||
|  |               style: Theme.of(context).textTheme.bodyMedium, | ||||||
|  |               textAlign: TextAlign.center, | ||||||
|  |             ), | ||||||
|  |             if (action != null) ...[ | ||||||
|  |               const SizedBox(height: 24), | ||||||
|  |               action!, | ||||||
|  |             ], | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | |||||||
| import 'package:image_picker/image_picker.dart'; | import 'package:image_picker/image_picker.dart'; | ||||||
| import 'package:island/models/file.dart'; | import 'package:island/models/file.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/models/publisher.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/services/file.dart'; | import 'package:island/services/file.dart'; | ||||||
|   | |||||||
| @@ -362,6 +362,7 @@ class PostItem extends HookConsumerWidget { | |||||||
|                           showModalBottomSheet( |                           showModalBottomSheet( | ||||||
|                             context: context, |                             context: context, | ||||||
|                             isScrollControlled: true, |                             isScrollControlled: true, | ||||||
|  |                             useRootNavigator: true, | ||||||
|                             builder: (context) => PostRepliesSheet(post: item), |                             builder: (context) => PostRepliesSheet(post: item), | ||||||
|                           ); |                           ); | ||||||
|                         } |                         } | ||||||
| @@ -535,20 +536,20 @@ Widget _buildReferencePost(BuildContext context, SnPost item) { | |||||||
|         ), |         ), | ||||||
|       ], |       ], | ||||||
|     ), |     ), | ||||||
|   ).gestures(onTap: () => context.push('/posts/referencePost.id')); |   ).gestures(onTap: () => context.push('/posts/${referencePost.id}')); | ||||||
| } | } | ||||||
|  |  | ||||||
| class PostReactionList extends HookConsumerWidget { | class PostReactionList extends HookConsumerWidget { | ||||||
|   final String parentId; |   final String parentId; | ||||||
|   final Map<String, int> reactions; |   final Map<String, int> reactions; | ||||||
|   final Function(String symbol, int attitude, int delta) onReact; |   final Function(String symbol, int attitude, int delta)? onReact; | ||||||
|   final EdgeInsets? padding; |   final EdgeInsets? padding; | ||||||
|   const PostReactionList({ |   const PostReactionList({ | ||||||
|     super.key, |     super.key, | ||||||
|     required this.parentId, |     required this.parentId, | ||||||
|     required this.reactions, |     required this.reactions, | ||||||
|     this.padding, |     this.padding, | ||||||
|     required this.onReact, |     this.onReact, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -569,7 +570,7 @@ class PostReactionList extends HookConsumerWidget { | |||||||
|           }) |           }) | ||||||
|           .then((resp) { |           .then((resp) { | ||||||
|             var isRemoving = resp.statusCode == 204; |             var isRemoving = resp.statusCode == 204; | ||||||
|             onReact(symbol, attitude, isRemoving ? -1 : 1); |             onReact?.call(symbol, attitude, isRemoving ? -1 : 1); | ||||||
|             HapticFeedback.heavyImpact(); |             HapticFeedback.heavyImpact(); | ||||||
|           }); |           }); | ||||||
|       submitting.value = false; |       submitting.value = false; | ||||||
| @@ -581,33 +582,34 @@ class PostReactionList extends HookConsumerWidget { | |||||||
|         scrollDirection: Axis.horizontal, |         scrollDirection: Axis.horizontal, | ||||||
|         padding: padding ?? EdgeInsets.zero, |         padding: padding ?? EdgeInsets.zero, | ||||||
|         children: [ |         children: [ | ||||||
|           Padding( |           if (onReact != null) | ||||||
|             padding: const EdgeInsets.only(right: 8), |             Padding( | ||||||
|             child: ActionChip( |               padding: const EdgeInsets.only(right: 8), | ||||||
|               avatar: Icon(Symbols.add_reaction), |               child: ActionChip( | ||||||
|               label: Text('react').tr(), |                 avatar: Icon(Symbols.add_reaction), | ||||||
|               visualDensity: const VisualDensity( |                 label: Text('react').tr(), | ||||||
|                 horizontal: VisualDensity.minimumDensity, |                 visualDensity: const VisualDensity( | ||||||
|                 vertical: VisualDensity.minimumDensity, |                   horizontal: VisualDensity.minimumDensity, | ||||||
|  |                   vertical: VisualDensity.minimumDensity, | ||||||
|  |                 ), | ||||||
|  |                 onPressed: | ||||||
|  |                     submitting.value | ||||||
|  |                         ? null | ||||||
|  |                         : () { | ||||||
|  |                           showModalBottomSheet( | ||||||
|  |                             context: context, | ||||||
|  |                             builder: (BuildContext context) { | ||||||
|  |                               return _PostReactionSheet( | ||||||
|  |                                 reactionsCount: reactions, | ||||||
|  |                                 onReact: (symbol, attitude) { | ||||||
|  |                                   reactPost(symbol, attitude); | ||||||
|  |                                 }, | ||||||
|  |                               ); | ||||||
|  |                             }, | ||||||
|  |                           ); | ||||||
|  |                         }, | ||||||
|               ), |               ), | ||||||
|               onPressed: |  | ||||||
|                   submitting.value |  | ||||||
|                       ? null |  | ||||||
|                       : () { |  | ||||||
|                         showModalBottomSheet( |  | ||||||
|                           context: context, |  | ||||||
|                           builder: (BuildContext context) { |  | ||||||
|                             return _PostReactionSheet( |  | ||||||
|                               reactionsCount: reactions, |  | ||||||
|                               onReact: (symbol, attitude) { |  | ||||||
|                                 reactPost(symbol, attitude); |  | ||||||
|                               }, |  | ||||||
|                             ); |  | ||||||
|                           }, |  | ||||||
|                         ); |  | ||||||
|                       }, |  | ||||||
|             ), |             ), | ||||||
|           ), |  | ||||||
|           for (final symbol in reactions.keys) |           for (final symbol in reactions.keys) | ||||||
|             Padding( |             Padding( | ||||||
|               padding: const EdgeInsets.only(right: 8), |               padding: const EdgeInsets.only(right: 8), | ||||||
|   | |||||||
| @@ -365,11 +365,6 @@ class PostItemCreator extends HookConsumerWidget { | |||||||
|           parentId: item.id, |           parentId: item.id, | ||||||
|           reactions: item.reactionsCount, |           reactions: item.reactionsCount, | ||||||
|           padding: EdgeInsets.zero, |           padding: EdgeInsets.zero, | ||||||
|           onReact: (symbol, attitude, delta) { |  | ||||||
|             final reactionsCount = Map<String, int>.from(item.reactionsCount); |  | ||||||
|             reactionsCount[symbol] = (reactionsCount[symbol] ?? 0) + delta; |  | ||||||
|             onUpdate?.call(item.copyWith(reactionsCount: reactionsCount)); |  | ||||||
|           }, |  | ||||||
|         ), |         ), | ||||||
|         const Gap(16), |         const Gap(16), | ||||||
|       ], |       ], | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
|  | import 'package:island/models/publisher.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/screens/creators/publishers.dart'; | import 'package:island/screens/creators/publishers.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import 'package:flutter/material.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/post.dart'; | import 'package:island/models/publisher.dart'; | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  |  | ||||||
| class PublisherCard extends ConsumerWidget { | class PublisherCard extends ConsumerWidget { | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ import 'package:island/models/file.dart'; | |||||||
| import 'package:island/pods/link_preview.dart'; | import 'package:island/pods/link_preview.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| import 'package:island/pods/userinfo.dart'; |  | ||||||
| import 'package:island/services/file.dart'; | import 'package:island/services/file.dart'; | ||||||
| import 'package:mime/mime.dart'; | import 'package:mime/mime.dart'; | ||||||
|  |  | ||||||
| @@ -193,7 +192,6 @@ class _ShareSheetState extends ConsumerState<ShareSheet> { | |||||||
|     setState(() => _isLoading = true); |     setState(() => _isLoading = true); | ||||||
|     try { |     try { | ||||||
|       final apiClient = ref.read(apiClientProvider); |       final apiClient = ref.read(apiClientProvider); | ||||||
|       final userInfo = ref.read(userInfoProvider.notifier); |  | ||||||
|       final serverUrl = ref.read(serverUrlProvider); |       final serverUrl = ref.read(serverUrlProvider); | ||||||
|  |  | ||||||
|       String content = _messageController.text.trim(); |       String content = _messageController.text.trim(); | ||||||
| @@ -218,7 +216,7 @@ class _ShareSheetState extends ConsumerState<ShareSheet> { | |||||||
|         case ShareContentType.file: |         case ShareContentType.file: | ||||||
|           // Upload files to cloud storage |           // Upload files to cloud storage | ||||||
|           if (widget.content.files?.isNotEmpty == true) { |           if (widget.content.files?.isNotEmpty == true) { | ||||||
|             final token = await userInfo.getAccessToken(); |             final token = ref.watch(tokenProvider)?.token; | ||||||
|             if (token == null) { |             if (token == null) { | ||||||
|               throw Exception('Authentication required'); |               throw Exception('Authentication required'); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev | |||||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||||
| # In Windows, build-name is used as the major, minor, and patch parts | # In Windows, build-name is used as the major, minor, and patch parts | ||||||
| # of the product and file versions while build-number is used as the build suffix. | # of the product and file versions while build-number is used as the build suffix. | ||||||
| version: 3.0.0+108 | version: 3.0.0+109 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ^3.7.2 |   sdk: ^3.7.2 | ||||||
| @@ -123,7 +123,7 @@ dependencies: | |||||||
|   receive_sharing_intent: ^1.8.1 |   receive_sharing_intent: ^1.8.1 | ||||||
|   top_snackbar_flutter: ^3.3.0 |   top_snackbar_flutter: ^3.3.0 | ||||||
|   textfield_tags: |   textfield_tags: | ||||||
|    git: |     git: | ||||||
|       url: https://github.com/lionelmennig/textfield_tags.git |       url: https://github.com/lionelmennig/textfield_tags.git | ||||||
|       ref: fixes/allow-controller-re-registration |       ref: fixes/allow-controller-re-registration | ||||||
|   mime: ^2.0.0 |   mime: ^2.0.0 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user