this repo has no description

Compare changes

Choose any two refs to compare.

+15730 -1577
+83
.github/instructions/api.instructions.md
···
··· 1 + --- 2 + applyTo: "lib/api.dart" 3 + --- 4 + 5 + # Copilot Instructions: Generate Typed Dart API Client for Grain Social Endpoints 6 + 7 + ## Goal 8 + 9 + Generate a Dart API client for the Grain social endpoints, using the lexicon 10 + JSON files in `lexicons/social/grain` and its subfolders. Each endpoint should 11 + have: 12 + 13 + - Typed request and response models 14 + - API methods with correct parameters and return types 15 + - Documentation from the lexicon descriptions 16 + 17 + ## Instructions for Copilot 18 + 19 + 1. **For each lexicon JSON file:** 20 + - Parse the endpoint definition (`id`, `type`, `description`, 21 + `parameters`/`input`, `output`). 22 + - Generate a Dart class for request parameters/input. 23 + - Generate a Dart class for response/output. 24 + - Use the [freezed](https://pub.dev/packages/freezed) package to generate an 25 + immutable model for each response type. 26 + - Each model class should be created in a separate file in 27 + `models/procedures`. 28 + - Create a Dart method for the endpoint, with correct types and 29 + documentation. 30 + - Each API method should: 31 + - Accept an `apiUrl` parameter as a prefix for requests (e.g., 32 + `$apiUrl/xrpc/${id}`). 33 + - Pass the API token in the `Authorization` header for all requests. 34 + - Use the endpoint URL format `/xrpc/${id}` (e.g., 35 + `/xrpc/social.grain.actor.getProfile`). 36 + 37 + 2. **Type Mapping:** 38 + - JSON `string` โ†’ Dart `String` 39 + - JSON `object` โ†’ Dart class 40 + - JSON `array` โ†’ Dart `List<T>` 41 + - JSON `integer` โ†’ Dart `int` 42 + - JSON `boolean` โ†’ Dart `bool` 43 + - JSON `*/*` (binary) โ†’ Dart `Uint8List` or `List<int>` 44 + 45 + 3. **API Method Example:** 46 + ```dart 47 + /// Get detailed profile view of an actor. 48 + Future<ProfileViewDetailed> getProfile(String actor); 49 + 50 + /// Create a comment. 51 + Future<CommentResponse> createComment(CreateCommentRequest request); 52 + 53 + /// Create a follow relationship. 54 + Future<FollowResponse> createFollow(String subject); 55 + 56 + /// Create a photo. 57 + Future<PhotoResponse> createPhoto(Uint8List photoData); 58 + ``` 59 + 60 + 4. **Documentation:** 61 + - Use the `description` field from the lexicon for method/class docs. 62 + 63 + 5. **Error Handling:** 64 + - Generate error classes/types for API errors. 65 + 66 + 6. **Authentication:** 67 + - Mark endpoints that require auth. 68 + 69 + ## Reference 70 + 71 + Use all JSON files in `lexicons/social/grain` and subfolders. For each endpoint, 72 + use the schema references for response types if available. 73 + 74 + ## Example Endpoints 75 + 76 + ### Get Actor Profile 77 + 78 + - **ID:** `social.grain.actor.getProfile` 79 + - **Type:** Query 80 + - **Description:** Get detailed profile view of an actor. Does not require auth, 81 + but contains relevant metadata with auth. 82 + - **Parameters:** `actor` (string, at-identifier) 83 + - **Response:** JSON, schema: `social.grain.actor.defs#profileViewDetailed`
+2 -1
.vscode/settings.json
··· 2 "editor.codeActionsOnSave": { 3 "source.organizeImports": "always" 4 }, 5 - "dart.lineLength": 100 6 }
··· 2 "editor.codeActionsOnSave": { 3 "source.organizeImports": "always" 4 }, 5 + "dart.lineLength": 100, 6 + "dart.flutterHotReloadOnSave": "all" 7 }
+21
LICENSE
···
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Grain Social 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+1 -1
android/app/build.gradle.kts
··· 21 22 defaultConfig { 23 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 - applicationId = "com.example.grain_flutter" 25 // You can update the following values to match your application needs. 26 // For more information, see: https://flutter.dev/to/review-gradle-config. 27 minSdk = flutter.minSdkVersion
··· 21 22 defaultConfig { 23 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 + applicationId = "social.grain.grain_flutter" 25 // You can update the following values to match your application needs. 26 // For more information, see: https://flutter.dev/to/review-gradle-config. 27 minSdk = flutter.minSdkVersion
+10
android/app/src/main/AndroidManifest.xml
··· 25 <category android:name="android.intent.category.LAUNCHER"/> 26 </intent-filter> 27 </activity> 28 <!-- Don't delete the meta-data below. 29 This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> 30 <meta-data
··· 25 <category android:name="android.intent.category.LAUNCHER"/> 26 </intent-filter> 27 </activity> 28 + <activity 29 + android:name="com.linusu.flutter_web_auth_2.CallbackActivity" 30 + android:exported="true"> 31 + <intent-filter android:label="flutter_web_auth_2"> 32 + <action android:name="android.intent.action.VIEW" /> 33 + <category android:name="android.intent.category.DEFAULT" /> 34 + <category android:name="android.intent.category.BROWSABLE" /> 35 + <data android:scheme="grainflutter" /> 36 + </intent-filter> 37 + </activity> 38 <!-- Don't delete the meta-data below. 39 This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> 40 <meta-data
android/app/src/main/res/mipmap-hdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-mdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xhdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png

This is a binary file and will not be displayed.

+2 -1
env.example
··· 1 - API_URL=https://grain.social
··· 1 + API_URL=https://grain.social 2 + WS_URL=wss://notifications.grainsocial.network/ws
+7
ios/Podfile.lock
··· 13 - FlutterMacOS 14 - share_plus (0.0.1): 15 - Flutter 16 - sqflite_darwin (0.0.4): 17 - Flutter 18 - FlutterMacOS ··· 27 - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 28 - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 29 - share_plus (from `.symlinks/plugins/share_plus/ios`) 30 - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) 31 - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 32 ··· 45 :path: ".symlinks/plugins/path_provider_foundation/darwin" 46 share_plus: 47 :path: ".symlinks/plugins/share_plus/ios" 48 sqflite_darwin: 49 :path: ".symlinks/plugins/sqflite_darwin/darwin" 50 url_launcher_ios: ··· 58 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 59 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 60 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f 61 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d 62 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe 63
··· 13 - FlutterMacOS 14 - share_plus (0.0.1): 15 - Flutter 16 + - shared_preferences_foundation (0.0.1): 17 + - Flutter 18 + - FlutterMacOS 19 - sqflite_darwin (0.0.4): 20 - Flutter 21 - FlutterMacOS ··· 30 - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 31 - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 32 - share_plus (from `.symlinks/plugins/share_plus/ios`) 33 + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 34 - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) 35 - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 36 ··· 49 :path: ".symlinks/plugins/path_provider_foundation/darwin" 50 share_plus: 51 :path: ".symlinks/plugins/share_plus/ios" 52 + shared_preferences_foundation: 53 + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 54 sqflite_darwin: 55 :path: ".symlinks/plugins/sqflite_darwin/darwin" 56 url_launcher_ios: ··· 64 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 65 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 66 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f 67 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 68 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d 69 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe 70
+3
ios/Runner.xcodeproj/project.pbxproj
··· 493 SWIFT_VERSION = 5.0; 494 TARGETED_DEVICE_FAMILY = 1; 495 VERSIONING_SYSTEM = "apple-generic"; 496 }; 497 name = Profile; 498 }; ··· 689 SWIFT_VERSION = 5.0; 690 TARGETED_DEVICE_FAMILY = 1; 691 VERSIONING_SYSTEM = "apple-generic"; 692 }; 693 name = Debug; 694 }; ··· 721 SWIFT_VERSION = 5.0; 722 TARGETED_DEVICE_FAMILY = 1; 723 VERSIONING_SYSTEM = "apple-generic"; 724 }; 725 name = Release; 726 };
··· 493 SWIFT_VERSION = 5.0; 494 TARGETED_DEVICE_FAMILY = 1; 495 VERSIONING_SYSTEM = "apple-generic"; 496 + WS_URL = "wss://notifications.grainsocial.network/ws"; 497 }; 498 name = Profile; 499 }; ··· 690 SWIFT_VERSION = 5.0; 691 TARGETED_DEVICE_FAMILY = 1; 692 VERSIONING_SYSTEM = "apple-generic"; 693 + WS_URL = "wss://notifications.grainsocial.network/ws"; 694 }; 695 name = Debug; 696 }; ··· 723 SWIFT_VERSION = 5.0; 724 TARGETED_DEVICE_FAMILY = 1; 725 VERSIONING_SYSTEM = "apple-generic"; 726 + WS_URL = "wss://notifications.grainsocial.network/ws"; 727 }; 728 name = Release; 729 };
+695
lexicons/app/bsky/actor/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.defs", 4 + "defs": { 5 + "nux": { 6 + "type": "object", 7 + "required": [ 8 + "id", 9 + "completed" 10 + ], 11 + "properties": { 12 + "id": { 13 + "type": "string", 14 + "maxLength": 100 15 + }, 16 + "data": { 17 + "type": "string", 18 + "maxLength": 3000, 19 + "description": "Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.", 20 + "maxGraphemes": 300 21 + }, 22 + "completed": { 23 + "type": "boolean", 24 + "default": false 25 + }, 26 + "expiresAt": { 27 + "type": "string", 28 + "format": "datetime", 29 + "description": "The date and time at which the NUX will expire and should be considered completed." 30 + } 31 + }, 32 + "description": "A new user experiences (NUX) storage object" 33 + }, 34 + "mutedWord": { 35 + "type": "object", 36 + "required": [ 37 + "value", 38 + "targets" 39 + ], 40 + "properties": { 41 + "id": { 42 + "type": "string" 43 + }, 44 + "value": { 45 + "type": "string", 46 + "maxLength": 10000, 47 + "description": "The muted word itself.", 48 + "maxGraphemes": 1000 49 + }, 50 + "targets": { 51 + "type": "array", 52 + "items": { 53 + "ref": "app.bsky.actor.defs#mutedWordTarget", 54 + "type": "ref" 55 + }, 56 + "description": "The intended targets of the muted word." 57 + }, 58 + "expiresAt": { 59 + "type": "string", 60 + "format": "datetime", 61 + "description": "The date and time at which the muted word will expire and no longer be applied." 62 + }, 63 + "actorTarget": { 64 + "type": "string", 65 + "default": "all", 66 + "description": "Groups of users to apply the muted word to. If undefined, applies to all users.", 67 + "knownValues": [ 68 + "all", 69 + "exclude-following" 70 + ] 71 + } 72 + }, 73 + "description": "A word that the account owner has muted." 74 + }, 75 + "savedFeed": { 76 + "type": "object", 77 + "required": [ 78 + "id", 79 + "type", 80 + "value", 81 + "pinned" 82 + ], 83 + "properties": { 84 + "id": { 85 + "type": "string" 86 + }, 87 + "type": { 88 + "type": "string", 89 + "knownValues": [ 90 + "feed", 91 + "list", 92 + "timeline" 93 + ] 94 + }, 95 + "value": { 96 + "type": "string" 97 + }, 98 + "pinned": { 99 + "type": "boolean" 100 + } 101 + } 102 + }, 103 + "preferences": { 104 + "type": "array", 105 + "items": { 106 + "refs": [ 107 + "#adultContentPref", 108 + "#contentLabelPref", 109 + "#savedFeedsPref", 110 + "#savedFeedsPrefV2", 111 + "#personalDetailsPref", 112 + "#feedViewPref", 113 + "#threadViewPref", 114 + "#interestsPref", 115 + "#mutedWordsPref", 116 + "#hiddenPostsPref", 117 + "#bskyAppStatePref", 118 + "#labelersPref", 119 + "#postInteractionSettingsPref" 120 + ], 121 + "type": "union" 122 + } 123 + }, 124 + "profileView": { 125 + "type": "object", 126 + "required": [ 127 + "did", 128 + "handle" 129 + ], 130 + "properties": { 131 + "did": { 132 + "type": "string", 133 + "format": "did" 134 + }, 135 + "avatar": { 136 + "type": "string", 137 + "format": "uri" 138 + }, 139 + "handle": { 140 + "type": "string", 141 + "format": "handle" 142 + }, 143 + "labels": { 144 + "type": "array", 145 + "items": { 146 + "ref": "com.atproto.label.defs#label", 147 + "type": "ref" 148 + } 149 + }, 150 + "viewer": { 151 + "ref": "#viewerState", 152 + "type": "ref" 153 + }, 154 + "createdAt": { 155 + "type": "string", 156 + "format": "datetime" 157 + }, 158 + "indexedAt": { 159 + "type": "string", 160 + "format": "datetime" 161 + }, 162 + "associated": { 163 + "ref": "#profileAssociated", 164 + "type": "ref" 165 + }, 166 + "description": { 167 + "type": "string", 168 + "maxLength": 2560, 169 + "maxGraphemes": 256 170 + }, 171 + "displayName": { 172 + "type": "string", 173 + "maxLength": 640, 174 + "maxGraphemes": 64 175 + } 176 + } 177 + }, 178 + "viewerState": { 179 + "type": "object", 180 + "properties": { 181 + "muted": { 182 + "type": "boolean" 183 + }, 184 + "blocking": { 185 + "type": "string", 186 + "format": "at-uri" 187 + }, 188 + "blockedBy": { 189 + "type": "boolean" 190 + }, 191 + "following": { 192 + "type": "string", 193 + "format": "at-uri" 194 + }, 195 + "followedBy": { 196 + "type": "string", 197 + "format": "at-uri" 198 + }, 199 + "mutedByList": { 200 + "ref": "app.bsky.graph.defs#listViewBasic", 201 + "type": "ref" 202 + }, 203 + "blockingByList": { 204 + "ref": "app.bsky.graph.defs#listViewBasic", 205 + "type": "ref" 206 + }, 207 + "knownFollowers": { 208 + "ref": "#knownFollowers", 209 + "type": "ref" 210 + } 211 + }, 212 + "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests." 213 + }, 214 + "feedViewPref": { 215 + "type": "object", 216 + "required": [ 217 + "feed" 218 + ], 219 + "properties": { 220 + "feed": { 221 + "type": "string", 222 + "description": "The URI of the feed, or an identifier which describes the feed." 223 + }, 224 + "hideReplies": { 225 + "type": "boolean", 226 + "description": "Hide replies in the feed." 227 + }, 228 + "hideReposts": { 229 + "type": "boolean", 230 + "description": "Hide reposts in the feed." 231 + }, 232 + "hideQuotePosts": { 233 + "type": "boolean", 234 + "description": "Hide quote posts in the feed." 235 + }, 236 + "hideRepliesByLikeCount": { 237 + "type": "integer", 238 + "description": "Hide replies in the feed if they do not have this number of likes." 239 + }, 240 + "hideRepliesByUnfollowed": { 241 + "type": "boolean", 242 + "default": true, 243 + "description": "Hide replies in the feed if they are not by followed users." 244 + } 245 + } 246 + }, 247 + "labelersPref": { 248 + "type": "object", 249 + "required": [ 250 + "labelers" 251 + ], 252 + "properties": { 253 + "labelers": { 254 + "type": "array", 255 + "items": { 256 + "ref": "#labelerPrefItem", 257 + "type": "ref" 258 + } 259 + } 260 + } 261 + }, 262 + "interestsPref": { 263 + "type": "object", 264 + "required": [ 265 + "tags" 266 + ], 267 + "properties": { 268 + "tags": { 269 + "type": "array", 270 + "items": { 271 + "type": "string", 272 + "maxLength": 640, 273 + "maxGraphemes": 64 274 + }, 275 + "maxLength": 100, 276 + "description": "A list of tags which describe the account owner's interests gathered during onboarding." 277 + } 278 + } 279 + }, 280 + "knownFollowers": { 281 + "type": "object", 282 + "required": [ 283 + "count", 284 + "followers" 285 + ], 286 + "properties": { 287 + "count": { 288 + "type": "integer" 289 + }, 290 + "followers": { 291 + "type": "array", 292 + "items": { 293 + "ref": "#profileViewBasic", 294 + "type": "ref" 295 + }, 296 + "maxLength": 5, 297 + "minLength": 0 298 + } 299 + }, 300 + "description": "The subject's followers whom you also follow" 301 + }, 302 + "mutedWordsPref": { 303 + "type": "object", 304 + "required": [ 305 + "items" 306 + ], 307 + "properties": { 308 + "items": { 309 + "type": "array", 310 + "items": { 311 + "ref": "app.bsky.actor.defs#mutedWord", 312 + "type": "ref" 313 + }, 314 + "description": "A list of words the account owner has muted." 315 + } 316 + } 317 + }, 318 + "savedFeedsPref": { 319 + "type": "object", 320 + "required": [ 321 + "pinned", 322 + "saved" 323 + ], 324 + "properties": { 325 + "saved": { 326 + "type": "array", 327 + "items": { 328 + "type": "string", 329 + "format": "at-uri" 330 + } 331 + }, 332 + "pinned": { 333 + "type": "array", 334 + "items": { 335 + "type": "string", 336 + "format": "at-uri" 337 + } 338 + }, 339 + "timelineIndex": { 340 + "type": "integer" 341 + } 342 + } 343 + }, 344 + "threadViewPref": { 345 + "type": "object", 346 + "properties": { 347 + "sort": { 348 + "type": "string", 349 + "description": "Sorting mode for threads.", 350 + "knownValues": [ 351 + "oldest", 352 + "newest", 353 + "most-likes", 354 + "random", 355 + "hotness" 356 + ] 357 + }, 358 + "prioritizeFollowedUsers": { 359 + "type": "boolean", 360 + "description": "Show followed users at the top of all replies." 361 + } 362 + } 363 + }, 364 + "hiddenPostsPref": { 365 + "type": "object", 366 + "required": [ 367 + "items" 368 + ], 369 + "properties": { 370 + "items": { 371 + "type": "array", 372 + "items": { 373 + "type": "string", 374 + "format": "at-uri" 375 + }, 376 + "description": "A list of URIs of posts the account owner has hidden." 377 + } 378 + } 379 + }, 380 + "labelerPrefItem": { 381 + "type": "object", 382 + "required": [ 383 + "did" 384 + ], 385 + "properties": { 386 + "did": { 387 + "type": "string", 388 + "format": "did" 389 + } 390 + } 391 + }, 392 + "mutedWordTarget": { 393 + "type": "string", 394 + "maxLength": 640, 395 + "knownValues": [ 396 + "content", 397 + "tag" 398 + ], 399 + "maxGraphemes": 64 400 + }, 401 + "adultContentPref": { 402 + "type": "object", 403 + "required": [ 404 + "enabled" 405 + ], 406 + "properties": { 407 + "enabled": { 408 + "type": "boolean", 409 + "default": false 410 + } 411 + } 412 + }, 413 + "bskyAppStatePref": { 414 + "type": "object", 415 + "properties": { 416 + "nuxs": { 417 + "type": "array", 418 + "items": { 419 + "ref": "app.bsky.actor.defs#nux", 420 + "type": "ref" 421 + }, 422 + "maxLength": 100, 423 + "description": "Storage for NUXs the user has encountered." 424 + }, 425 + "queuedNudges": { 426 + "type": "array", 427 + "items": { 428 + "type": "string", 429 + "maxLength": 100 430 + }, 431 + "maxLength": 1000, 432 + "description": "An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user." 433 + }, 434 + "activeProgressGuide": { 435 + "ref": "#bskyAppProgressGuide", 436 + "type": "ref" 437 + } 438 + }, 439 + "description": "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this." 440 + }, 441 + "contentLabelPref": { 442 + "type": "object", 443 + "required": [ 444 + "label", 445 + "visibility" 446 + ], 447 + "properties": { 448 + "label": { 449 + "type": "string" 450 + }, 451 + "labelerDid": { 452 + "type": "string", 453 + "format": "did", 454 + "description": "Which labeler does this preference apply to? If undefined, applies globally." 455 + }, 456 + "visibility": { 457 + "type": "string", 458 + "knownValues": [ 459 + "ignore", 460 + "show", 461 + "warn", 462 + "hide" 463 + ] 464 + } 465 + } 466 + }, 467 + "profileViewBasic": { 468 + "type": "object", 469 + "required": [ 470 + "did", 471 + "handle" 472 + ], 473 + "properties": { 474 + "did": { 475 + "type": "string", 476 + "format": "did" 477 + }, 478 + "avatar": { 479 + "type": "string", 480 + "format": "uri" 481 + }, 482 + "handle": { 483 + "type": "string", 484 + "format": "handle" 485 + }, 486 + "labels": { 487 + "type": "array", 488 + "items": { 489 + "ref": "com.atproto.label.defs#label", 490 + "type": "ref" 491 + } 492 + }, 493 + "viewer": { 494 + "ref": "#viewerState", 495 + "type": "ref" 496 + }, 497 + "createdAt": { 498 + "type": "string", 499 + "format": "datetime" 500 + }, 501 + "associated": { 502 + "ref": "#profileAssociated", 503 + "type": "ref" 504 + }, 505 + "displayName": { 506 + "type": "string", 507 + "maxLength": 640, 508 + "maxGraphemes": 64 509 + } 510 + } 511 + }, 512 + "savedFeedsPrefV2": { 513 + "type": "object", 514 + "required": [ 515 + "items" 516 + ], 517 + "properties": { 518 + "items": { 519 + "type": "array", 520 + "items": { 521 + "ref": "app.bsky.actor.defs#savedFeed", 522 + "type": "ref" 523 + } 524 + } 525 + } 526 + }, 527 + "profileAssociated": { 528 + "type": "object", 529 + "properties": { 530 + "chat": { 531 + "ref": "#profileAssociatedChat", 532 + "type": "ref" 533 + }, 534 + "lists": { 535 + "type": "integer" 536 + }, 537 + "labeler": { 538 + "type": "boolean" 539 + }, 540 + "feedgens": { 541 + "type": "integer" 542 + }, 543 + "starterPacks": { 544 + "type": "integer" 545 + } 546 + } 547 + }, 548 + "personalDetailsPref": { 549 + "type": "object", 550 + "properties": { 551 + "birthDate": { 552 + "type": "string", 553 + "format": "datetime", 554 + "description": "The birth date of account owner." 555 + } 556 + } 557 + }, 558 + "profileViewDetailed": { 559 + "type": "object", 560 + "required": [ 561 + "did", 562 + "handle" 563 + ], 564 + "properties": { 565 + "did": { 566 + "type": "string", 567 + "format": "did" 568 + }, 569 + "avatar": { 570 + "type": "string", 571 + "format": "uri" 572 + }, 573 + "banner": { 574 + "type": "string", 575 + "format": "uri" 576 + }, 577 + "handle": { 578 + "type": "string", 579 + "format": "handle" 580 + }, 581 + "labels": { 582 + "type": "array", 583 + "items": { 584 + "ref": "com.atproto.label.defs#label", 585 + "type": "ref" 586 + } 587 + }, 588 + "viewer": { 589 + "ref": "#viewerState", 590 + "type": "ref" 591 + }, 592 + "createdAt": { 593 + "type": "string", 594 + "format": "datetime" 595 + }, 596 + "indexedAt": { 597 + "type": "string", 598 + "format": "datetime" 599 + }, 600 + "associated": { 601 + "ref": "#profileAssociated", 602 + "type": "ref" 603 + }, 604 + "pinnedPost": { 605 + "ref": "com.atproto.repo.strongRef", 606 + "type": "ref" 607 + }, 608 + "postsCount": { 609 + "type": "integer" 610 + }, 611 + "description": { 612 + "type": "string", 613 + "maxLength": 2560, 614 + "maxGraphemes": 256 615 + }, 616 + "displayName": { 617 + "type": "string", 618 + "maxLength": 640, 619 + "maxGraphemes": 64 620 + }, 621 + "followsCount": { 622 + "type": "integer" 623 + }, 624 + "followersCount": { 625 + "type": "integer" 626 + }, 627 + "joinedViaStarterPack": { 628 + "ref": "app.bsky.graph.defs#starterPackViewBasic", 629 + "type": "ref" 630 + } 631 + } 632 + }, 633 + "bskyAppProgressGuide": { 634 + "type": "object", 635 + "required": [ 636 + "guide" 637 + ], 638 + "properties": { 639 + "guide": { 640 + "type": "string", 641 + "maxLength": 100 642 + } 643 + }, 644 + "description": "If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress." 645 + }, 646 + "profileAssociatedChat": { 647 + "type": "object", 648 + "required": [ 649 + "allowIncoming" 650 + ], 651 + "properties": { 652 + "allowIncoming": { 653 + "type": "string", 654 + "knownValues": [ 655 + "all", 656 + "none", 657 + "following" 658 + ] 659 + } 660 + } 661 + }, 662 + "postInteractionSettingsPref": { 663 + "type": "object", 664 + "required": [], 665 + "properties": { 666 + "threadgateAllowRules": { 667 + "type": "array", 668 + "items": { 669 + "refs": [ 670 + "app.bsky.feed.threadgate#mentionRule", 671 + "app.bsky.feed.threadgate#followerRule", 672 + "app.bsky.feed.threadgate#followingRule", 673 + "app.bsky.feed.threadgate#listRule" 674 + ], 675 + "type": "union" 676 + }, 677 + "maxLength": 5, 678 + "description": "Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply." 679 + }, 680 + "postgateEmbeddingRules": { 681 + "type": "array", 682 + "items": { 683 + "refs": [ 684 + "app.bsky.feed.postgate#disableRule" 685 + ], 686 + "type": "union" 687 + }, 688 + "maxLength": 5, 689 + "description": "Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed." 690 + } 691 + }, 692 + "description": "Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly." 693 + } 694 + } 695 + }
+64
lexicons/app/bsky/actor/profile.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.profile", 4 + "defs": { 5 + "main": { 6 + "key": "literal:self", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "properties": { 11 + "avatar": { 12 + "type": "blob", 13 + "accept": [ 14 + "image/png", 15 + "image/jpeg" 16 + ], 17 + "maxSize": 1000000, 18 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'" 19 + }, 20 + "banner": { 21 + "type": "blob", 22 + "accept": [ 23 + "image/png", 24 + "image/jpeg" 25 + ], 26 + "maxSize": 1000000, 27 + "description": "Larger horizontal image to display behind profile view." 28 + }, 29 + "labels": { 30 + "refs": [ 31 + "com.atproto.label.defs#selfLabels" 32 + ], 33 + "type": "union", 34 + "description": "Self-label values, specific to the Bluesky application, on the overall account." 35 + }, 36 + "createdAt": { 37 + "type": "string", 38 + "format": "datetime" 39 + }, 40 + "pinnedPost": { 41 + "ref": "com.atproto.repo.strongRef", 42 + "type": "ref" 43 + }, 44 + "description": { 45 + "type": "string", 46 + "maxLength": 2560, 47 + "description": "Free-form profile description text.", 48 + "maxGraphemes": 256 49 + }, 50 + "displayName": { 51 + "type": "string", 52 + "maxLength": 640, 53 + "maxGraphemes": 64 54 + }, 55 + "joinedViaStarterPack": { 56 + "ref": "com.atproto.repo.strongRef", 57 + "type": "ref" 58 + } 59 + } 60 + }, 61 + "description": "A declaration of a Bluesky account profile." 62 + } 63 + } 64 + }
+24
lexicons/app/bsky/embed/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.defs", 4 + "defs": { 5 + "aspectRatio": { 6 + "type": "object", 7 + "required": [ 8 + "width", 9 + "height" 10 + ], 11 + "properties": { 12 + "width": { 13 + "type": "integer", 14 + "minimum": 1 15 + }, 16 + "height": { 17 + "type": "integer", 18 + "minimum": 1 19 + } 20 + }, 21 + "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit." 22 + } 23 + } 24 + }
+82
lexicons/app/bsky/embed/external.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.external", 4 + "defs": { 5 + "main": { 6 + "type": "object", 7 + "required": [ 8 + "external" 9 + ], 10 + "properties": { 11 + "external": { 12 + "ref": "#external", 13 + "type": "ref" 14 + } 15 + }, 16 + "description": "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post)." 17 + }, 18 + "view": { 19 + "type": "object", 20 + "required": [ 21 + "external" 22 + ], 23 + "properties": { 24 + "external": { 25 + "ref": "#viewExternal", 26 + "type": "ref" 27 + } 28 + } 29 + }, 30 + "external": { 31 + "type": "object", 32 + "required": [ 33 + "uri", 34 + "title", 35 + "description" 36 + ], 37 + "properties": { 38 + "uri": { 39 + "type": "string", 40 + "format": "uri" 41 + }, 42 + "thumb": { 43 + "type": "blob", 44 + "accept": [ 45 + "image/*" 46 + ], 47 + "maxSize": 1000000 48 + }, 49 + "title": { 50 + "type": "string" 51 + }, 52 + "description": { 53 + "type": "string" 54 + } 55 + } 56 + }, 57 + "viewExternal": { 58 + "type": "object", 59 + "required": [ 60 + "uri", 61 + "title", 62 + "description" 63 + ], 64 + "properties": { 65 + "uri": { 66 + "type": "string", 67 + "format": "uri" 68 + }, 69 + "thumb": { 70 + "type": "string", 71 + "format": "uri" 72 + }, 73 + "title": { 74 + "type": "string" 75 + }, 76 + "description": { 77 + "type": "string" 78 + } 79 + } 80 + } 81 + } 82 + }
+91
lexicons/app/bsky/embed/images.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.images", 4 + "description": "A set of images embedded in a Bluesky record (eg, a post).", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "images" 10 + ], 11 + "properties": { 12 + "images": { 13 + "type": "array", 14 + "items": { 15 + "ref": "#image", 16 + "type": "ref" 17 + }, 18 + "maxLength": 4 19 + } 20 + } 21 + }, 22 + "view": { 23 + "type": "object", 24 + "required": [ 25 + "images" 26 + ], 27 + "properties": { 28 + "images": { 29 + "type": "array", 30 + "items": { 31 + "ref": "#viewImage", 32 + "type": "ref" 33 + }, 34 + "maxLength": 4 35 + } 36 + } 37 + }, 38 + "image": { 39 + "type": "object", 40 + "required": [ 41 + "image", 42 + "alt" 43 + ], 44 + "properties": { 45 + "alt": { 46 + "type": "string", 47 + "description": "Alt text description of the image, for accessibility." 48 + }, 49 + "image": { 50 + "type": "blob", 51 + "accept": [ 52 + "image/*" 53 + ], 54 + "maxSize": 1000000 55 + }, 56 + "aspectRatio": { 57 + "ref": "app.bsky.embed.defs#aspectRatio", 58 + "type": "ref" 59 + } 60 + } 61 + }, 62 + "viewImage": { 63 + "type": "object", 64 + "required": [ 65 + "thumb", 66 + "fullsize", 67 + "alt" 68 + ], 69 + "properties": { 70 + "alt": { 71 + "type": "string", 72 + "description": "Alt text description of the image, for accessibility." 73 + }, 74 + "thumb": { 75 + "type": "string", 76 + "format": "uri", 77 + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." 78 + }, 79 + "fullsize": { 80 + "type": "string", 81 + "format": "uri", 82 + "description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View." 83 + }, 84 + "aspectRatio": { 85 + "ref": "app.bsky.embed.defs#aspectRatio", 86 + "type": "ref" 87 + } 88 + } 89 + } 90 + } 91 + }
+160
lexicons/app/bsky/embed/record.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.record", 4 + "description": "A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "record" 10 + ], 11 + "properties": { 12 + "record": { 13 + "ref": "com.atproto.repo.strongRef", 14 + "type": "ref" 15 + } 16 + } 17 + }, 18 + "view": { 19 + "type": "object", 20 + "required": [ 21 + "record" 22 + ], 23 + "properties": { 24 + "record": { 25 + "refs": [ 26 + "#viewRecord", 27 + "#viewNotFound", 28 + "#viewBlocked", 29 + "#viewDetached", 30 + "app.bsky.feed.defs#generatorView", 31 + "app.bsky.graph.defs#listView", 32 + "app.bsky.labeler.defs#labelerView", 33 + "app.bsky.graph.defs#starterPackViewBasic" 34 + ], 35 + "type": "union" 36 + } 37 + } 38 + }, 39 + "viewRecord": { 40 + "type": "object", 41 + "required": [ 42 + "uri", 43 + "cid", 44 + "author", 45 + "value", 46 + "indexedAt" 47 + ], 48 + "properties": { 49 + "cid": { 50 + "type": "string", 51 + "format": "cid" 52 + }, 53 + "uri": { 54 + "type": "string", 55 + "format": "at-uri" 56 + }, 57 + "value": { 58 + "type": "unknown", 59 + "description": "The record data itself." 60 + }, 61 + "author": { 62 + "ref": "app.bsky.actor.defs#profileViewBasic", 63 + "type": "ref" 64 + }, 65 + "embeds": { 66 + "type": "array", 67 + "items": { 68 + "refs": [ 69 + "app.bsky.embed.images#view", 70 + "app.bsky.embed.video#view", 71 + "app.bsky.embed.external#view", 72 + "app.bsky.embed.record#view", 73 + "app.bsky.embed.recordWithMedia#view" 74 + ], 75 + "type": "union" 76 + } 77 + }, 78 + "labels": { 79 + "type": "array", 80 + "items": { 81 + "ref": "com.atproto.label.defs#label", 82 + "type": "ref" 83 + } 84 + }, 85 + "indexedAt": { 86 + "type": "string", 87 + "format": "datetime" 88 + }, 89 + "likeCount": { 90 + "type": "integer" 91 + }, 92 + "quoteCount": { 93 + "type": "integer" 94 + }, 95 + "replyCount": { 96 + "type": "integer" 97 + }, 98 + "repostCount": { 99 + "type": "integer" 100 + } 101 + } 102 + }, 103 + "viewBlocked": { 104 + "type": "object", 105 + "required": [ 106 + "uri", 107 + "blocked", 108 + "author" 109 + ], 110 + "properties": { 111 + "uri": { 112 + "type": "string", 113 + "format": "at-uri" 114 + }, 115 + "author": { 116 + "ref": "app.bsky.feed.defs#blockedAuthor", 117 + "type": "ref" 118 + }, 119 + "blocked": { 120 + "type": "boolean", 121 + "const": true 122 + } 123 + } 124 + }, 125 + "viewDetached": { 126 + "type": "object", 127 + "required": [ 128 + "uri", 129 + "detached" 130 + ], 131 + "properties": { 132 + "uri": { 133 + "type": "string", 134 + "format": "at-uri" 135 + }, 136 + "detached": { 137 + "type": "boolean", 138 + "const": true 139 + } 140 + } 141 + }, 142 + "viewNotFound": { 143 + "type": "object", 144 + "required": [ 145 + "uri", 146 + "notFound" 147 + ], 148 + "properties": { 149 + "uri": { 150 + "type": "string", 151 + "format": "at-uri" 152 + }, 153 + "notFound": { 154 + "type": "boolean", 155 + "const": true 156 + } 157 + } 158 + } 159 + } 160 + }
+49
lexicons/app/bsky/embed/recordWithMedia.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.recordWithMedia", 4 + "description": "A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "record", 10 + "media" 11 + ], 12 + "properties": { 13 + "media": { 14 + "refs": [ 15 + "app.bsky.embed.images", 16 + "app.bsky.embed.video", 17 + "app.bsky.embed.external" 18 + ], 19 + "type": "union" 20 + }, 21 + "record": { 22 + "ref": "app.bsky.embed.record", 23 + "type": "ref" 24 + } 25 + } 26 + }, 27 + "view": { 28 + "type": "object", 29 + "required": [ 30 + "record", 31 + "media" 32 + ], 33 + "properties": { 34 + "media": { 35 + "refs": [ 36 + "app.bsky.embed.images#view", 37 + "app.bsky.embed.video#view", 38 + "app.bsky.embed.external#view" 39 + ], 40 + "type": "union" 41 + }, 42 + "record": { 43 + "ref": "app.bsky.embed.record#view", 44 + "type": "ref" 45 + } 46 + } 47 + } 48 + } 49 + }
+90
lexicons/app/bsky/embed/video.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.embed.video", 4 + "description": "A video embedded in a Bluesky record (eg, a post).", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "video" 10 + ], 11 + "properties": { 12 + "alt": { 13 + "type": "string", 14 + "maxLength": 10000, 15 + "description": "Alt text description of the video, for accessibility.", 16 + "maxGraphemes": 1000 17 + }, 18 + "video": { 19 + "type": "blob", 20 + "accept": [ 21 + "video/mp4" 22 + ], 23 + "maxSize": 50000000 24 + }, 25 + "captions": { 26 + "type": "array", 27 + "items": { 28 + "ref": "#caption", 29 + "type": "ref" 30 + }, 31 + "maxLength": 20 32 + }, 33 + "aspectRatio": { 34 + "ref": "app.bsky.embed.defs#aspectRatio", 35 + "type": "ref" 36 + } 37 + } 38 + }, 39 + "view": { 40 + "type": "object", 41 + "required": [ 42 + "cid", 43 + "playlist" 44 + ], 45 + "properties": { 46 + "alt": { 47 + "type": "string", 48 + "maxLength": 10000, 49 + "maxGraphemes": 1000 50 + }, 51 + "cid": { 52 + "type": "string", 53 + "format": "cid" 54 + }, 55 + "playlist": { 56 + "type": "string", 57 + "format": "uri" 58 + }, 59 + "thumbnail": { 60 + "type": "string", 61 + "format": "uri" 62 + }, 63 + "aspectRatio": { 64 + "ref": "app.bsky.embed.defs#aspectRatio", 65 + "type": "ref" 66 + } 67 + } 68 + }, 69 + "caption": { 70 + "type": "object", 71 + "required": [ 72 + "lang", 73 + "file" 74 + ], 75 + "properties": { 76 + "file": { 77 + "type": "blob", 78 + "accept": [ 79 + "text/vtt" 80 + ], 81 + "maxSize": 20000 82 + }, 83 + "lang": { 84 + "type": "string", 85 + "format": "language" 86 + } 87 + } 88 + } 89 + } 90 + }
+515
lexicons/app/bsky/feed/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.feed.defs", 4 + "defs": { 5 + "postView": { 6 + "type": "object", 7 + "required": [ 8 + "uri", 9 + "cid", 10 + "author", 11 + "record", 12 + "indexedAt" 13 + ], 14 + "properties": { 15 + "cid": { 16 + "type": "string", 17 + "format": "cid" 18 + }, 19 + "uri": { 20 + "type": "string", 21 + "format": "at-uri" 22 + }, 23 + "embed": { 24 + "refs": [ 25 + "app.bsky.embed.images#view", 26 + "app.bsky.embed.video#view", 27 + "app.bsky.embed.external#view", 28 + "app.bsky.embed.record#view", 29 + "app.bsky.embed.recordWithMedia#view" 30 + ], 31 + "type": "union" 32 + }, 33 + "author": { 34 + "ref": "app.bsky.actor.defs#profileViewBasic", 35 + "type": "ref" 36 + }, 37 + "labels": { 38 + "type": "array", 39 + "items": { 40 + "ref": "com.atproto.label.defs#label", 41 + "type": "ref" 42 + } 43 + }, 44 + "record": { 45 + "type": "unknown" 46 + }, 47 + "viewer": { 48 + "ref": "#viewerState", 49 + "type": "ref" 50 + }, 51 + "indexedAt": { 52 + "type": "string", 53 + "format": "datetime" 54 + }, 55 + "likeCount": { 56 + "type": "integer" 57 + }, 58 + "quoteCount": { 59 + "type": "integer" 60 + }, 61 + "replyCount": { 62 + "type": "integer" 63 + }, 64 + "threadgate": { 65 + "ref": "#threadgateView", 66 + "type": "ref" 67 + }, 68 + "repostCount": { 69 + "type": "integer" 70 + } 71 + } 72 + }, 73 + "replyRef": { 74 + "type": "object", 75 + "required": [ 76 + "root", 77 + "parent" 78 + ], 79 + "properties": { 80 + "root": { 81 + "refs": [ 82 + "#postView", 83 + "#notFoundPost", 84 + "#blockedPost" 85 + ], 86 + "type": "union" 87 + }, 88 + "parent": { 89 + "refs": [ 90 + "#postView", 91 + "#notFoundPost", 92 + "#blockedPost" 93 + ], 94 + "type": "union" 95 + }, 96 + "grandparentAuthor": { 97 + "ref": "app.bsky.actor.defs#profileViewBasic", 98 + "type": "ref", 99 + "description": "When parent is a reply to another post, this is the author of that post." 100 + } 101 + } 102 + }, 103 + "reasonPin": { 104 + "type": "object", 105 + "properties": {} 106 + }, 107 + "blockedPost": { 108 + "type": "object", 109 + "required": [ 110 + "uri", 111 + "blocked", 112 + "author" 113 + ], 114 + "properties": { 115 + "uri": { 116 + "type": "string", 117 + "format": "at-uri" 118 + }, 119 + "author": { 120 + "ref": "#blockedAuthor", 121 + "type": "ref" 122 + }, 123 + "blocked": { 124 + "type": "boolean", 125 + "const": true 126 + } 127 + } 128 + }, 129 + "interaction": { 130 + "type": "object", 131 + "properties": { 132 + "item": { 133 + "type": "string", 134 + "format": "at-uri" 135 + }, 136 + "event": { 137 + "type": "string", 138 + "knownValues": [ 139 + "app.bsky.feed.defs#requestLess", 140 + "app.bsky.feed.defs#requestMore", 141 + "app.bsky.feed.defs#clickthroughItem", 142 + "app.bsky.feed.defs#clickthroughAuthor", 143 + "app.bsky.feed.defs#clickthroughReposter", 144 + "app.bsky.feed.defs#clickthroughEmbed", 145 + "app.bsky.feed.defs#interactionSeen", 146 + "app.bsky.feed.defs#interactionLike", 147 + "app.bsky.feed.defs#interactionRepost", 148 + "app.bsky.feed.defs#interactionReply", 149 + "app.bsky.feed.defs#interactionQuote", 150 + "app.bsky.feed.defs#interactionShare" 151 + ] 152 + }, 153 + "feedContext": { 154 + "type": "string", 155 + "maxLength": 2000, 156 + "description": "Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton." 157 + } 158 + } 159 + }, 160 + "requestLess": { 161 + "type": "token", 162 + "description": "Request that less content like the given feed item be shown in the feed" 163 + }, 164 + "requestMore": { 165 + "type": "token", 166 + "description": "Request that more content like the given feed item be shown in the feed" 167 + }, 168 + "viewerState": { 169 + "type": "object", 170 + "properties": { 171 + "like": { 172 + "type": "string", 173 + "format": "at-uri" 174 + }, 175 + "pinned": { 176 + "type": "boolean" 177 + }, 178 + "repost": { 179 + "type": "string", 180 + "format": "at-uri" 181 + }, 182 + "threadMuted": { 183 + "type": "boolean" 184 + }, 185 + "replyDisabled": { 186 + "type": "boolean" 187 + }, 188 + "embeddingDisabled": { 189 + "type": "boolean" 190 + } 191 + }, 192 + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests." 193 + }, 194 + "feedViewPost": { 195 + "type": "object", 196 + "required": [ 197 + "post" 198 + ], 199 + "properties": { 200 + "post": { 201 + "ref": "#postView", 202 + "type": "ref" 203 + }, 204 + "reply": { 205 + "ref": "#replyRef", 206 + "type": "ref" 207 + }, 208 + "reason": { 209 + "refs": [ 210 + "#reasonRepost", 211 + "#reasonPin" 212 + ], 213 + "type": "union" 214 + }, 215 + "feedContext": { 216 + "type": "string", 217 + "maxLength": 2000, 218 + "description": "Context provided by feed generator that may be passed back alongside interactions." 219 + } 220 + } 221 + }, 222 + "notFoundPost": { 223 + "type": "object", 224 + "required": [ 225 + "uri", 226 + "notFound" 227 + ], 228 + "properties": { 229 + "uri": { 230 + "type": "string", 231 + "format": "at-uri" 232 + }, 233 + "notFound": { 234 + "type": "boolean", 235 + "const": true 236 + } 237 + } 238 + }, 239 + "reasonRepost": { 240 + "type": "object", 241 + "required": [ 242 + "by", 243 + "indexedAt" 244 + ], 245 + "properties": { 246 + "by": { 247 + "ref": "app.bsky.actor.defs#profileViewBasic", 248 + "type": "ref" 249 + }, 250 + "indexedAt": { 251 + "type": "string", 252 + "format": "datetime" 253 + } 254 + } 255 + }, 256 + "blockedAuthor": { 257 + "type": "object", 258 + "required": [ 259 + "did" 260 + ], 261 + "properties": { 262 + "did": { 263 + "type": "string", 264 + "format": "did" 265 + }, 266 + "viewer": { 267 + "ref": "app.bsky.actor.defs#viewerState", 268 + "type": "ref" 269 + } 270 + } 271 + }, 272 + "generatorView": { 273 + "type": "object", 274 + "required": [ 275 + "uri", 276 + "cid", 277 + "did", 278 + "creator", 279 + "displayName", 280 + "indexedAt" 281 + ], 282 + "properties": { 283 + "cid": { 284 + "type": "string", 285 + "format": "cid" 286 + }, 287 + "did": { 288 + "type": "string", 289 + "format": "did" 290 + }, 291 + "uri": { 292 + "type": "string", 293 + "format": "at-uri" 294 + }, 295 + "avatar": { 296 + "type": "string", 297 + "format": "uri" 298 + }, 299 + "labels": { 300 + "type": "array", 301 + "items": { 302 + "ref": "com.atproto.label.defs#label", 303 + "type": "ref" 304 + } 305 + }, 306 + "viewer": { 307 + "ref": "#generatorViewerState", 308 + "type": "ref" 309 + }, 310 + "creator": { 311 + "ref": "app.bsky.actor.defs#profileView", 312 + "type": "ref" 313 + }, 314 + "indexedAt": { 315 + "type": "string", 316 + "format": "datetime" 317 + }, 318 + "likeCount": { 319 + "type": "integer", 320 + "minimum": 0 321 + }, 322 + "contentMode": { 323 + "type": "string", 324 + "knownValues": [ 325 + "app.bsky.feed.defs#contentModeUnspecified", 326 + "app.bsky.feed.defs#contentModeVideo" 327 + ] 328 + }, 329 + "description": { 330 + "type": "string", 331 + "maxLength": 3000, 332 + "maxGraphemes": 300 333 + }, 334 + "displayName": { 335 + "type": "string" 336 + }, 337 + "descriptionFacets": { 338 + "type": "array", 339 + "items": { 340 + "ref": "app.bsky.richtext.facet", 341 + "type": "ref" 342 + } 343 + }, 344 + "acceptsInteractions": { 345 + "type": "boolean" 346 + } 347 + } 348 + }, 349 + "threadContext": { 350 + "type": "object", 351 + "properties": { 352 + "rootAuthorLike": { 353 + "type": "string", 354 + "format": "at-uri" 355 + } 356 + }, 357 + "description": "Metadata about this post within the context of the thread it is in." 358 + }, 359 + "threadViewPost": { 360 + "type": "object", 361 + "required": [ 362 + "post" 363 + ], 364 + "properties": { 365 + "post": { 366 + "ref": "#postView", 367 + "type": "ref" 368 + }, 369 + "parent": { 370 + "refs": [ 371 + "#threadViewPost", 372 + "#notFoundPost", 373 + "#blockedPost" 374 + ], 375 + "type": "union" 376 + }, 377 + "replies": { 378 + "type": "array", 379 + "items": { 380 + "refs": [ 381 + "#threadViewPost", 382 + "#notFoundPost", 383 + "#blockedPost" 384 + ], 385 + "type": "union" 386 + } 387 + }, 388 + "threadContext": { 389 + "ref": "#threadContext", 390 + "type": "ref" 391 + } 392 + } 393 + }, 394 + "threadgateView": { 395 + "type": "object", 396 + "properties": { 397 + "cid": { 398 + "type": "string", 399 + "format": "cid" 400 + }, 401 + "uri": { 402 + "type": "string", 403 + "format": "at-uri" 404 + }, 405 + "lists": { 406 + "type": "array", 407 + "items": { 408 + "ref": "app.bsky.graph.defs#listViewBasic", 409 + "type": "ref" 410 + } 411 + }, 412 + "record": { 413 + "type": "unknown" 414 + } 415 + } 416 + }, 417 + "interactionLike": { 418 + "type": "token", 419 + "description": "User liked the feed item" 420 + }, 421 + "interactionSeen": { 422 + "type": "token", 423 + "description": "Feed item was seen by user" 424 + }, 425 + "clickthroughItem": { 426 + "type": "token", 427 + "description": "User clicked through to the feed item" 428 + }, 429 + "contentModeVideo": { 430 + "type": "token", 431 + "description": "Declares the feed generator returns posts containing app.bsky.embed.video embeds." 432 + }, 433 + "interactionQuote": { 434 + "type": "token", 435 + "description": "User quoted the feed item" 436 + }, 437 + "interactionReply": { 438 + "type": "token", 439 + "description": "User replied to the feed item" 440 + }, 441 + "interactionShare": { 442 + "type": "token", 443 + "description": "User shared the feed item" 444 + }, 445 + "skeletonFeedPost": { 446 + "type": "object", 447 + "required": [ 448 + "post" 449 + ], 450 + "properties": { 451 + "post": { 452 + "type": "string", 453 + "format": "at-uri" 454 + }, 455 + "reason": { 456 + "refs": [ 457 + "#skeletonReasonRepost", 458 + "#skeletonReasonPin" 459 + ], 460 + "type": "union" 461 + }, 462 + "feedContext": { 463 + "type": "string", 464 + "maxLength": 2000, 465 + "description": "Context that will be passed through to client and may be passed to feed generator back alongside interactions." 466 + } 467 + } 468 + }, 469 + "clickthroughEmbed": { 470 + "type": "token", 471 + "description": "User clicked through to the embedded content of the feed item" 472 + }, 473 + "interactionRepost": { 474 + "type": "token", 475 + "description": "User reposted the feed item" 476 + }, 477 + "skeletonReasonPin": { 478 + "type": "object", 479 + "properties": {} 480 + }, 481 + "clickthroughAuthor": { 482 + "type": "token", 483 + "description": "User clicked through to the author of the feed item" 484 + }, 485 + "clickthroughReposter": { 486 + "type": "token", 487 + "description": "User clicked through to the reposter of the feed item" 488 + }, 489 + "generatorViewerState": { 490 + "type": "object", 491 + "properties": { 492 + "like": { 493 + "type": "string", 494 + "format": "at-uri" 495 + } 496 + } 497 + }, 498 + "skeletonReasonRepost": { 499 + "type": "object", 500 + "required": [ 501 + "repost" 502 + ], 503 + "properties": { 504 + "repost": { 505 + "type": "string", 506 + "format": "at-uri" 507 + } 508 + } 509 + }, 510 + "contentModeUnspecified": { 511 + "type": "token", 512 + "description": "Declares the feed generator returns any types of posts." 513 + } 514 + } 515 + }
+54
lexicons/app/bsky/feed/postgate.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.feed.postgate", 4 + "defs": { 5 + "main": { 6 + "key": "tid", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "post", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "post": { 16 + "type": "string", 17 + "format": "at-uri", 18 + "description": "Reference (AT-URI) to the post record." 19 + }, 20 + "createdAt": { 21 + "type": "string", 22 + "format": "datetime" 23 + }, 24 + "embeddingRules": { 25 + "type": "array", 26 + "items": { 27 + "refs": [ 28 + "#disableRule" 29 + ], 30 + "type": "union" 31 + }, 32 + "maxLength": 5, 33 + "description": "List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed." 34 + }, 35 + "detachedEmbeddingUris": { 36 + "type": "array", 37 + "items": { 38 + "type": "string", 39 + "format": "at-uri" 40 + }, 41 + "maxLength": 50, 42 + "description": "List of AT-URIs embedding this post that the author has detached from." 43 + } 44 + } 45 + }, 46 + "description": "Record defining interaction rules for a post. The record key (rkey) of the postgate record must match the record key of the post, and that record must be in the same repository." 47 + }, 48 + "disableRule": { 49 + "type": "object", 50 + "properties": {}, 51 + "description": "Disables embedding of this post." 52 + } 53 + } 54 + }
+80
lexicons/app/bsky/feed/threadgate.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.feed.threadgate", 4 + "defs": { 5 + "main": { 6 + "key": "tid", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "post", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "post": { 16 + "type": "string", 17 + "format": "at-uri", 18 + "description": "Reference (AT-URI) to the post record." 19 + }, 20 + "allow": { 21 + "type": "array", 22 + "items": { 23 + "refs": [ 24 + "#mentionRule", 25 + "#followerRule", 26 + "#followingRule", 27 + "#listRule" 28 + ], 29 + "type": "union" 30 + }, 31 + "maxLength": 5, 32 + "description": "List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply." 33 + }, 34 + "createdAt": { 35 + "type": "string", 36 + "format": "datetime" 37 + }, 38 + "hiddenReplies": { 39 + "type": "array", 40 + "items": { 41 + "type": "string", 42 + "format": "at-uri" 43 + }, 44 + "maxLength": 50, 45 + "description": "List of hidden reply URIs." 46 + } 47 + } 48 + }, 49 + "description": "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository." 50 + }, 51 + "listRule": { 52 + "type": "object", 53 + "required": [ 54 + "list" 55 + ], 56 + "properties": { 57 + "list": { 58 + "type": "string", 59 + "format": "at-uri" 60 + } 61 + }, 62 + "description": "Allow replies from actors on a list." 63 + }, 64 + "mentionRule": { 65 + "type": "object", 66 + "properties": {}, 67 + "description": "Allow replies from actors mentioned in your post." 68 + }, 69 + "followerRule": { 70 + "type": "object", 71 + "properties": {}, 72 + "description": "Allow replies from actors who follow you." 73 + }, 74 + "followingRule": { 75 + "type": "object", 76 + "properties": {}, 77 + "description": "Allow replies from actors you follow." 78 + } 79 + } 80 + }
+332
lexicons/app/bsky/graph/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.graph.defs", 4 + "defs": { 5 + "modlist": { 6 + "type": "token", 7 + "description": "A list of actors to apply an aggregate moderation action (mute/block) on." 8 + }, 9 + "listView": { 10 + "type": "object", 11 + "required": [ 12 + "uri", 13 + "cid", 14 + "creator", 15 + "name", 16 + "purpose", 17 + "indexedAt" 18 + ], 19 + "properties": { 20 + "cid": { 21 + "type": "string", 22 + "format": "cid" 23 + }, 24 + "uri": { 25 + "type": "string", 26 + "format": "at-uri" 27 + }, 28 + "name": { 29 + "type": "string", 30 + "maxLength": 64, 31 + "minLength": 1 32 + }, 33 + "avatar": { 34 + "type": "string", 35 + "format": "uri" 36 + }, 37 + "labels": { 38 + "type": "array", 39 + "items": { 40 + "ref": "com.atproto.label.defs#label", 41 + "type": "ref" 42 + } 43 + }, 44 + "viewer": { 45 + "ref": "#listViewerState", 46 + "type": "ref" 47 + }, 48 + "creator": { 49 + "ref": "app.bsky.actor.defs#profileView", 50 + "type": "ref" 51 + }, 52 + "purpose": { 53 + "ref": "#listPurpose", 54 + "type": "ref" 55 + }, 56 + "indexedAt": { 57 + "type": "string", 58 + "format": "datetime" 59 + }, 60 + "description": { 61 + "type": "string", 62 + "maxLength": 3000, 63 + "maxGraphemes": 300 64 + }, 65 + "listItemCount": { 66 + "type": "integer", 67 + "minimum": 0 68 + }, 69 + "descriptionFacets": { 70 + "type": "array", 71 + "items": { 72 + "ref": "app.bsky.richtext.facet", 73 + "type": "ref" 74 + } 75 + } 76 + } 77 + }, 78 + "curatelist": { 79 + "type": "token", 80 + "description": "A list of actors used for curation purposes such as list feeds or interaction gating." 81 + }, 82 + "listPurpose": { 83 + "type": "string", 84 + "knownValues": [ 85 + "app.bsky.graph.defs#modlist", 86 + "app.bsky.graph.defs#curatelist", 87 + "app.bsky.graph.defs#referencelist" 88 + ] 89 + }, 90 + "listItemView": { 91 + "type": "object", 92 + "required": [ 93 + "uri", 94 + "subject" 95 + ], 96 + "properties": { 97 + "uri": { 98 + "type": "string", 99 + "format": "at-uri" 100 + }, 101 + "subject": { 102 + "ref": "app.bsky.actor.defs#profileView", 103 + "type": "ref" 104 + } 105 + } 106 + }, 107 + "relationship": { 108 + "type": "object", 109 + "required": [ 110 + "did" 111 + ], 112 + "properties": { 113 + "did": { 114 + "type": "string", 115 + "format": "did" 116 + }, 117 + "following": { 118 + "type": "string", 119 + "format": "at-uri", 120 + "description": "if the actor follows this DID, this is the AT-URI of the follow record" 121 + }, 122 + "followedBy": { 123 + "type": "string", 124 + "format": "at-uri", 125 + "description": "if the actor is followed by this DID, contains the AT-URI of the follow record" 126 + } 127 + }, 128 + "description": "lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object)" 129 + }, 130 + "listViewBasic": { 131 + "type": "object", 132 + "required": [ 133 + "uri", 134 + "cid", 135 + "name", 136 + "purpose" 137 + ], 138 + "properties": { 139 + "cid": { 140 + "type": "string", 141 + "format": "cid" 142 + }, 143 + "uri": { 144 + "type": "string", 145 + "format": "at-uri" 146 + }, 147 + "name": { 148 + "type": "string", 149 + "maxLength": 64, 150 + "minLength": 1 151 + }, 152 + "avatar": { 153 + "type": "string", 154 + "format": "uri" 155 + }, 156 + "labels": { 157 + "type": "array", 158 + "items": { 159 + "ref": "com.atproto.label.defs#label", 160 + "type": "ref" 161 + } 162 + }, 163 + "viewer": { 164 + "ref": "#listViewerState", 165 + "type": "ref" 166 + }, 167 + "purpose": { 168 + "ref": "#listPurpose", 169 + "type": "ref" 170 + }, 171 + "indexedAt": { 172 + "type": "string", 173 + "format": "datetime" 174 + }, 175 + "listItemCount": { 176 + "type": "integer", 177 + "minimum": 0 178 + } 179 + } 180 + }, 181 + "notFoundActor": { 182 + "type": "object", 183 + "required": [ 184 + "actor", 185 + "notFound" 186 + ], 187 + "properties": { 188 + "actor": { 189 + "type": "string", 190 + "format": "at-identifier" 191 + }, 192 + "notFound": { 193 + "type": "boolean", 194 + "const": true 195 + } 196 + }, 197 + "description": "indicates that a handle or DID could not be resolved" 198 + }, 199 + "referencelist": { 200 + "type": "token", 201 + "description": "A list of actors used for only for reference purposes such as within a starter pack." 202 + }, 203 + "listViewerState": { 204 + "type": "object", 205 + "properties": { 206 + "muted": { 207 + "type": "boolean" 208 + }, 209 + "blocked": { 210 + "type": "string", 211 + "format": "at-uri" 212 + } 213 + } 214 + }, 215 + "starterPackView": { 216 + "type": "object", 217 + "required": [ 218 + "uri", 219 + "cid", 220 + "record", 221 + "creator", 222 + "indexedAt" 223 + ], 224 + "properties": { 225 + "cid": { 226 + "type": "string", 227 + "format": "cid" 228 + }, 229 + "uri": { 230 + "type": "string", 231 + "format": "at-uri" 232 + }, 233 + "list": { 234 + "ref": "#listViewBasic", 235 + "type": "ref" 236 + }, 237 + "feeds": { 238 + "type": "array", 239 + "items": { 240 + "ref": "app.bsky.feed.defs#generatorView", 241 + "type": "ref" 242 + }, 243 + "maxLength": 3 244 + }, 245 + "labels": { 246 + "type": "array", 247 + "items": { 248 + "ref": "com.atproto.label.defs#label", 249 + "type": "ref" 250 + } 251 + }, 252 + "record": { 253 + "type": "unknown" 254 + }, 255 + "creator": { 256 + "ref": "app.bsky.actor.defs#profileViewBasic", 257 + "type": "ref" 258 + }, 259 + "indexedAt": { 260 + "type": "string", 261 + "format": "datetime" 262 + }, 263 + "joinedWeekCount": { 264 + "type": "integer", 265 + "minimum": 0 266 + }, 267 + "listItemsSample": { 268 + "type": "array", 269 + "items": { 270 + "ref": "#listItemView", 271 + "type": "ref" 272 + }, 273 + "maxLength": 12 274 + }, 275 + "joinedAllTimeCount": { 276 + "type": "integer", 277 + "minimum": 0 278 + } 279 + } 280 + }, 281 + "starterPackViewBasic": { 282 + "type": "object", 283 + "required": [ 284 + "uri", 285 + "cid", 286 + "record", 287 + "creator", 288 + "indexedAt" 289 + ], 290 + "properties": { 291 + "cid": { 292 + "type": "string", 293 + "format": "cid" 294 + }, 295 + "uri": { 296 + "type": "string", 297 + "format": "at-uri" 298 + }, 299 + "labels": { 300 + "type": "array", 301 + "items": { 302 + "ref": "com.atproto.label.defs#label", 303 + "type": "ref" 304 + } 305 + }, 306 + "record": { 307 + "type": "unknown" 308 + }, 309 + "creator": { 310 + "ref": "app.bsky.actor.defs#profileViewBasic", 311 + "type": "ref" 312 + }, 313 + "indexedAt": { 314 + "type": "string", 315 + "format": "datetime" 316 + }, 317 + "listItemCount": { 318 + "type": "integer", 319 + "minimum": 0 320 + }, 321 + "joinedWeekCount": { 322 + "type": "integer", 323 + "minimum": 0 324 + }, 325 + "joinedAllTimeCount": { 326 + "type": "integer", 327 + "minimum": 0 328 + } 329 + } 330 + } 331 + } 332 + }
+28
lexicons/app/bsky/graph/follow.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.graph.follow", 4 + "defs": { 5 + "main": { 6 + "key": "tid", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "subject", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "subject": { 16 + "type": "string", 17 + "format": "did" 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + } 23 + } 24 + }, 25 + "description": "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView." 26 + } 27 + } 28 + }
+128
lexicons/app/bsky/labeler/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.labeler.defs", 4 + "defs": { 5 + "labelerView": { 6 + "type": "object", 7 + "required": [ 8 + "uri", 9 + "cid", 10 + "creator", 11 + "indexedAt" 12 + ], 13 + "properties": { 14 + "cid": { 15 + "type": "string", 16 + "format": "cid" 17 + }, 18 + "uri": { 19 + "type": "string", 20 + "format": "at-uri" 21 + }, 22 + "labels": { 23 + "type": "array", 24 + "items": { 25 + "ref": "com.atproto.label.defs#label", 26 + "type": "ref" 27 + } 28 + }, 29 + "viewer": { 30 + "ref": "#labelerViewerState", 31 + "type": "ref" 32 + }, 33 + "creator": { 34 + "ref": "app.bsky.actor.defs#profileView", 35 + "type": "ref" 36 + }, 37 + "indexedAt": { 38 + "type": "string", 39 + "format": "datetime" 40 + }, 41 + "likeCount": { 42 + "type": "integer", 43 + "minimum": 0 44 + } 45 + } 46 + }, 47 + "labelerPolicies": { 48 + "type": "object", 49 + "required": [ 50 + "labelValues" 51 + ], 52 + "properties": { 53 + "labelValues": { 54 + "type": "array", 55 + "items": { 56 + "ref": "com.atproto.label.defs#labelValue", 57 + "type": "ref" 58 + }, 59 + "description": "The label values which this labeler publishes. May include global or custom labels." 60 + }, 61 + "labelValueDefinitions": { 62 + "type": "array", 63 + "items": { 64 + "ref": "com.atproto.label.defs#labelValueDefinition", 65 + "type": "ref" 66 + }, 67 + "description": "Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler." 68 + } 69 + } 70 + }, 71 + "labelerViewerState": { 72 + "type": "object", 73 + "properties": { 74 + "like": { 75 + "type": "string", 76 + "format": "at-uri" 77 + } 78 + } 79 + }, 80 + "labelerViewDetailed": { 81 + "type": "object", 82 + "required": [ 83 + "uri", 84 + "cid", 85 + "creator", 86 + "policies", 87 + "indexedAt" 88 + ], 89 + "properties": { 90 + "cid": { 91 + "type": "string", 92 + "format": "cid" 93 + }, 94 + "uri": { 95 + "type": "string", 96 + "format": "at-uri" 97 + }, 98 + "labels": { 99 + "type": "array", 100 + "items": { 101 + "ref": "com.atproto.label.defs#label", 102 + "type": "ref" 103 + } 104 + }, 105 + "viewer": { 106 + "ref": "#labelerViewerState", 107 + "type": "ref" 108 + }, 109 + "creator": { 110 + "ref": "app.bsky.actor.defs#profileView", 111 + "type": "ref" 112 + }, 113 + "policies": { 114 + "ref": "app.bsky.labeler.defs#labelerPolicies", 115 + "type": "ref" 116 + }, 117 + "indexedAt": { 118 + "type": "string", 119 + "format": "datetime" 120 + }, 121 + "likeCount": { 122 + "type": "integer", 123 + "minimum": 0 124 + } 125 + } 126 + } 127 + } 128 + }
+89
lexicons/app/bsky/richtext/facet.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.richtext.facet", 4 + "defs": { 5 + "tag": { 6 + "type": "object", 7 + "required": [ 8 + "tag" 9 + ], 10 + "properties": { 11 + "tag": { 12 + "type": "string", 13 + "maxLength": 640, 14 + "maxGraphemes": 64 15 + } 16 + }, 17 + "description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags')." 18 + }, 19 + "link": { 20 + "type": "object", 21 + "required": [ 22 + "uri" 23 + ], 24 + "properties": { 25 + "uri": { 26 + "type": "string", 27 + "format": "uri" 28 + } 29 + }, 30 + "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL." 31 + }, 32 + "main": { 33 + "type": "object", 34 + "required": [ 35 + "index", 36 + "features" 37 + ], 38 + "properties": { 39 + "index": { 40 + "ref": "#byteSlice", 41 + "type": "ref" 42 + }, 43 + "features": { 44 + "type": "array", 45 + "items": { 46 + "refs": [ 47 + "#mention", 48 + "#link", 49 + "#tag" 50 + ], 51 + "type": "union" 52 + } 53 + } 54 + }, 55 + "description": "Annotation of a sub-string within rich text." 56 + }, 57 + "mention": { 58 + "type": "object", 59 + "required": [ 60 + "did" 61 + ], 62 + "properties": { 63 + "did": { 64 + "type": "string", 65 + "format": "did" 66 + } 67 + }, 68 + "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID." 69 + }, 70 + "byteSlice": { 71 + "type": "object", 72 + "required": [ 73 + "byteStart", 74 + "byteEnd" 75 + ], 76 + "properties": { 77 + "byteEnd": { 78 + "type": "integer", 79 + "minimum": 0 80 + }, 81 + "byteStart": { 82 + "type": "integer", 83 + "minimum": 0 84 + } 85 + }, 86 + "description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets." 87 + } 88 + } 89 + }
+192
lexicons/com/atproto/label/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.label.defs", 4 + "defs": { 5 + "label": { 6 + "type": "object", 7 + "required": [ 8 + "src", 9 + "uri", 10 + "val", 11 + "cts" 12 + ], 13 + "properties": { 14 + "cid": { 15 + "type": "string", 16 + "format": "cid", 17 + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." 18 + }, 19 + "cts": { 20 + "type": "string", 21 + "format": "datetime", 22 + "description": "Timestamp when this label was created." 23 + }, 24 + "exp": { 25 + "type": "string", 26 + "format": "datetime", 27 + "description": "Timestamp at which this label expires (no longer applies)." 28 + }, 29 + "neg": { 30 + "type": "boolean", 31 + "description": "If true, this is a negation label, overwriting a previous label." 32 + }, 33 + "sig": { 34 + "type": "bytes", 35 + "description": "Signature of dag-cbor encoded label." 36 + }, 37 + "src": { 38 + "type": "string", 39 + "format": "did", 40 + "description": "DID of the actor who created this label." 41 + }, 42 + "uri": { 43 + "type": "string", 44 + "format": "uri", 45 + "description": "AT URI of the record, repository (account), or other resource that this label applies to." 46 + }, 47 + "val": { 48 + "type": "string", 49 + "maxLength": 128, 50 + "description": "The short string name of the value or type of this label." 51 + }, 52 + "ver": { 53 + "type": "integer", 54 + "description": "The AT Protocol version of the label object." 55 + } 56 + }, 57 + "description": "Metadata tag on an atproto resource (eg, repo or record)." 58 + }, 59 + "selfLabel": { 60 + "type": "object", 61 + "required": [ 62 + "val" 63 + ], 64 + "properties": { 65 + "val": { 66 + "type": "string", 67 + "maxLength": 128, 68 + "description": "The short string name of the value or type of this label." 69 + } 70 + }, 71 + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel." 72 + }, 73 + "labelValue": { 74 + "type": "string", 75 + "knownValues": [ 76 + "!hide", 77 + "!no-promote", 78 + "!warn", 79 + "!no-unauthenticated", 80 + "dmca-violation", 81 + "doxxing", 82 + "porn", 83 + "sexual", 84 + "nudity", 85 + "nsfl", 86 + "gore" 87 + ] 88 + }, 89 + "selfLabels": { 90 + "type": "object", 91 + "required": [ 92 + "values" 93 + ], 94 + "properties": { 95 + "values": { 96 + "type": "array", 97 + "items": { 98 + "ref": "#selfLabel", 99 + "type": "ref" 100 + }, 101 + "maxLength": 10 102 + } 103 + }, 104 + "description": "Metadata tags on an atproto record, published by the author within the record." 105 + }, 106 + "labelValueDefinition": { 107 + "type": "object", 108 + "required": [ 109 + "identifier", 110 + "severity", 111 + "blurs", 112 + "locales" 113 + ], 114 + "properties": { 115 + "blurs": { 116 + "type": "string", 117 + "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 118 + "knownValues": [ 119 + "content", 120 + "media", 121 + "none" 122 + ] 123 + }, 124 + "locales": { 125 + "type": "array", 126 + "items": { 127 + "ref": "#labelValueDefinitionStrings", 128 + "type": "ref" 129 + } 130 + }, 131 + "severity": { 132 + "type": "string", 133 + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 134 + "knownValues": [ 135 + "inform", 136 + "alert", 137 + "none" 138 + ] 139 + }, 140 + "adultOnly": { 141 + "type": "boolean", 142 + "description": "Does the user need to have adult content enabled in order to configure this label?" 143 + }, 144 + "identifier": { 145 + "type": "string", 146 + "maxLength": 100, 147 + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 148 + "maxGraphemes": 100 149 + }, 150 + "defaultSetting": { 151 + "type": "string", 152 + "default": "warn", 153 + "description": "The default setting for this label.", 154 + "knownValues": [ 155 + "ignore", 156 + "warn", 157 + "hide" 158 + ] 159 + } 160 + }, 161 + "description": "Declares a label value and its expected interpretations and behaviors." 162 + }, 163 + "labelValueDefinitionStrings": { 164 + "type": "object", 165 + "required": [ 166 + "lang", 167 + "name", 168 + "description" 169 + ], 170 + "properties": { 171 + "lang": { 172 + "type": "string", 173 + "format": "language", 174 + "description": "The code of the language these strings are written in." 175 + }, 176 + "name": { 177 + "type": "string", 178 + "maxLength": 640, 179 + "description": "A short human-readable name for the label.", 180 + "maxGraphemes": 64 181 + }, 182 + "description": { 183 + "type": "string", 184 + "maxLength": 100000, 185 + "description": "A longer description of what the label means and why it might be applied.", 186 + "maxGraphemes": 10000 187 + } 188 + }, 189 + "description": "Strings which describe the label in the UI, localized into a specific language." 190 + } 191 + } 192 + }
+55
lexicons/com/atproto/moderation/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.moderation.defs", 4 + "defs": { 5 + "reasonRude": { 6 + "type": "token", 7 + "description": "Rude, harassing, explicit, or otherwise unwelcoming behavior" 8 + }, 9 + "reasonSpam": { 10 + "type": "token", 11 + "description": "Spam: frequent unwanted promotion, replies, mentions" 12 + }, 13 + "reasonType": { 14 + "type": "string", 15 + "knownValues": [ 16 + "com.atproto.moderation.defs#reasonSpam", 17 + "com.atproto.moderation.defs#reasonViolation", 18 + "com.atproto.moderation.defs#reasonMisleading", 19 + "com.atproto.moderation.defs#reasonSexual", 20 + "com.atproto.moderation.defs#reasonRude", 21 + "com.atproto.moderation.defs#reasonOther", 22 + "com.atproto.moderation.defs#reasonAppeal" 23 + ] 24 + }, 25 + "reasonOther": { 26 + "type": "token", 27 + "description": "Other: reports not falling under another report category" 28 + }, 29 + "subjectType": { 30 + "type": "string", 31 + "description": "Tag describing a type of subject that might be reported.", 32 + "knownValues": [ 33 + "account", 34 + "record", 35 + "chat" 36 + ] 37 + }, 38 + "reasonAppeal": { 39 + "type": "token", 40 + "description": "Appeal: appeal a previously taken moderation action" 41 + }, 42 + "reasonSexual": { 43 + "type": "token", 44 + "description": "Unwanted or mislabeled sexual content" 45 + }, 46 + "reasonViolation": { 47 + "type": "token", 48 + "description": "Direct violation of server rules, laws, terms of service" 49 + }, 50 + "reasonMisleading": { 51 + "type": "token", 52 + "description": "Misleading identity, affiliation, or content" 53 + } 54 + } 55 + }
+24
lexicons/com/atproto/repo/strongRef.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": [ 9 + "uri", 10 + "cid" 11 + ], 12 + "properties": { 13 + "cid": { 14 + "type": "string", 15 + "format": "cid" 16 + }, 17 + "uri": { 18 + "type": "string", 19 + "format": "at-uri" 20 + } 21 + } 22 + } 23 + } 24 + }
+71
lexicons/sh/tangled/actor/profile.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of a Tangled account profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "bluesky" 13 + ], 14 + "properties": { 15 + "description": { 16 + "type": "string", 17 + "description": "Free-form profile description text.", 18 + "maxGraphemes": 256, 19 + "maxLength": 2560 20 + }, 21 + "links": { 22 + "type": "array", 23 + "minLength": 0, 24 + "maxLength": 5, 25 + "items": { 26 + "type": "string", 27 + "description": "Any URI, intended for social profiles or websites, can be used to link DIDs/AT-URIs too." 28 + } 29 + }, 30 + "stats": { 31 + "type": "array", 32 + "minLength": 0, 33 + "maxLength": 2, 34 + "items": { 35 + "type": "string", 36 + "description": "Vanity stats.", 37 + "enum": [ 38 + "merged-pull-request-count", 39 + "closed-pull-request-count", 40 + "open-pull-request-count", 41 + "open-issue-count", 42 + "closed-issue-count", 43 + "repository-count" 44 + ] 45 + } 46 + }, 47 + "bluesky": { 48 + "type": "boolean", 49 + "description": "Include link to this account on Bluesky." 50 + }, 51 + "location": { 52 + "type": "string", 53 + "description": "Free-form location text.", 54 + "maxGraphemes": 40, 55 + "maxLength": 400 56 + }, 57 + "pinnedRepositories": { 58 + "type": "array", 59 + "description": "Any ATURI, it is up to appviews to validate these fields.", 60 + "minLength": 0, 61 + "maxLength": 6, 62 + "items": { 63 + "type": "string", 64 + "format": "at-uri" 65 + } 66 + } 67 + } 68 + } 69 + } 70 + } 71 + }
+27
lexicons/sh/tangled/graph/follow.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.graph.follow", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "subject", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "subject": { 16 + "type": "string", 17 + "format": "did" 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + } 23 + } 24 + } 25 + } 26 + } 27 + }
+77
lexicons/social/grain/actor/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.defs", 4 + "defs": { 5 + "profileView": { 6 + "type": "object", 7 + "required": ["cid", "did", "handle"], 8 + "properties": { 9 + "cid": { "type": "string", "format": "cid" }, 10 + "did": { "type": "string", "format": "did" }, 11 + "handle": { "type": "string", "format": "handle" }, 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "maxLength": 2560, 20 + "maxGraphemes": 256 21 + }, 22 + "labels": { 23 + "type": "array", 24 + "items": { 25 + "ref": "com.atproto.label.defs#label", 26 + "type": "ref" 27 + } 28 + }, 29 + "avatar": { "type": "string", "format": "uri" }, 30 + "createdAt": { "type": "string", "format": "datetime" } 31 + } 32 + }, 33 + "profileViewDetailed": { 34 + "type": "object", 35 + "required": ["cid", "did", "handle"], 36 + "properties": { 37 + "cid": { "type": "string", "format": "cid" }, 38 + "did": { "type": "string", "format": "did" }, 39 + "handle": { "type": "string", "format": "handle" }, 40 + "displayName": { 41 + "type": "string", 42 + "maxGraphemes": 64, 43 + "maxLength": 640 44 + }, 45 + "description": { 46 + "type": "string", 47 + "maxGraphemes": 256, 48 + "maxLength": 2560 49 + }, 50 + "avatar": { "type": "string", "format": "uri" }, 51 + "cameras": { 52 + "type": "array", 53 + "items": { "type": "string" }, 54 + "description": "List of camera make and models used by this actor derived from EXIF data of photos linked to galleries." 55 + }, 56 + "followersCount": { "type": "integer" }, 57 + "followsCount": { "type": "integer" }, 58 + "galleryCount": { "type": "integer" }, 59 + "indexedAt": { "type": "string", "format": "datetime" }, 60 + "createdAt": { "type": "string", "format": "datetime" }, 61 + "viewer": { "type": "ref", "ref": "#viewerState" }, 62 + "labels": { 63 + "type": "array", 64 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 65 + } 66 + } 67 + }, 68 + "viewerState": { 69 + "type": "object", 70 + "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", 71 + "properties": { 72 + "following": { "type": "string", "format": "at-uri" }, 73 + "followedBy": { "type": "string", "format": "at-uri" } 74 + } 75 + } 76 + } 77 + }
+41
lexicons/social/grain/actor/getActorFavs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.getActorFavs", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a view of an actor's favorite galleries. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { "type": "string", "format": "at-identifier" }, 13 + "limit": { 14 + "type": "integer", 15 + "minimum": 1, 16 + "maximum": 100, 17 + "default": 50 18 + }, 19 + "cursor": { "type": "string" } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["items"], 27 + "properties": { 28 + "cursor": { "type": "string" }, 29 + "items": { 30 + "type": "array", 31 + "items": { 32 + "type": "ref", 33 + "ref": "social.grain.gallery.defs#galleryView" 34 + } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + } 41 + }
+28
lexicons/social/grain/actor/getProfile.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.getProfile", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "at-identifier", 15 + "description": "Handle or DID of account to fetch profile of." 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "ref", 23 + "ref": "social.grain.actor.defs#profileViewDetailed" 24 + } 25 + } 26 + } 27 + } 28 + }
+34
lexicons/social/grain/actor/profile.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of a basic account profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "properties": { 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "description": "Free-form profile description text.", 20 + "maxGraphemes": 256, 21 + "maxLength": 2560 22 + }, 23 + "avatar": { 24 + "type": "blob", 25 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", 26 + "accept": ["image/png", "image/jpeg"], 27 + "maxSize": 1000000 28 + }, 29 + "createdAt": { "type": "string", "format": "datetime" } 30 + } 31 + } 32 + } 33 + } 34 + }
+43
lexicons/social/grain/actor/searchActors.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.searchActors", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Find actors (profiles) matching search criteria. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "q": { 12 + "type": "string", 13 + "description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." 14 + }, 15 + "limit": { 16 + "type": "integer", 17 + "minimum": 1, 18 + "maximum": 100, 19 + "default": 25 20 + }, 21 + "cursor": { "type": "string" } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "application/json", 26 + "schema": { 27 + "type": "object", 28 + "required": ["actors"], 29 + "properties": { 30 + "cursor": { "type": "string" }, 31 + "actors": { 32 + "type": "array", 33 + "items": { 34 + "type": "ref", 35 + "ref": "social.grain.actor.defs#profileView" 36 + } 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + }
+25
lexicons/social/grain/actor/updateAvatar.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.updateAvatar", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Update an actor's avatar. Requires auth.", 8 + "input": { 9 + "encoding": "*/*" 10 + }, 11 + "output": { 12 + "encoding": "application/json", 13 + "schema": { 14 + "type": "object", 15 + "properties": { 16 + "success": { 17 + "type": "boolean", 18 + "description": "Indicates whether the avatar update was successful." 19 + } 20 + } 21 + } 22 + } 23 + } 24 + } 25 + }
+41
lexicons/social/grain/actor/updateProfile.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.actor.updateProfile", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Update an actor's profile info. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "properties": { 13 + "displayName": { 14 + "type": "string", 15 + "maxGraphemes": 64, 16 + "maxLength": 640 17 + }, 18 + "description": { 19 + "type": "string", 20 + "description": "Free-form profile description text.", 21 + "maxGraphemes": 256, 22 + "maxLength": 2560 23 + } 24 + } 25 + } 26 + }, 27 + "output": { 28 + "encoding": "application/json", 29 + "schema": { 30 + "type": "object", 31 + "properties": { 32 + "success": { 33 + "type": "boolean", 34 + "description": "Indicates whether the profile update was successful." 35 + } 36 + } 37 + } 38 + } 39 + } 40 + } 41 + }
+42
lexicons/social/grain/comment/comment.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.comment", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["text", "subject", "createdAt"], 11 + "properties": { 12 + "text": { 13 + "type": "string", 14 + "maxLength": 3000, 15 + "maxGraphemes": 300 16 + }, 17 + "facets": { 18 + "type": "array", 19 + "description": "Annotations of description text (mentions and URLs, hashtags, etc)", 20 + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } 21 + }, 22 + "subject": { 23 + "type": "string", 24 + "format": "at-uri" 25 + }, 26 + "focus": { 27 + "type": "string", 28 + "format": "at-uri" 29 + }, 30 + "replyTo": { 31 + "type": "string", 32 + "format": "at-uri" 33 + }, 34 + "createdAt": { 35 + "type": "string", 36 + "format": "datetime" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + }
+49
lexicons/social/grain/comment/createComment.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.comment.createComment", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a comment. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["text", "subject"], 13 + "properties": { 14 + "text": { 15 + "type": "string", 16 + "maxLength": 3000, 17 + "maxGraphemes": 300 18 + }, 19 + "subject": { 20 + "type": "string", 21 + "format": "at-uri" 22 + }, 23 + "focus": { 24 + "type": "string", 25 + "format": "at-uri" 26 + }, 27 + "replyTo": { 28 + "type": "string", 29 + "format": "at-uri" 30 + } 31 + } 32 + } 33 + }, 34 + "output": { 35 + "encoding": "application/json", 36 + "schema": { 37 + "type": "object", 38 + "properties": { 39 + "commentUri": { 40 + "type": "string", 41 + "format": "at-uri", 42 + "description": "AT URI of the created comment" 43 + } 44 + } 45 + } 46 + } 47 + } 48 + } 49 + }
+52
lexicons/social/grain/comment/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.comment.defs", 4 + "defs": { 5 + "commentView": { 6 + "type": "object", 7 + "required": ["uri", "cid", "author", "text", "createdAt"], 8 + "properties": { 9 + "uri": { "type": "string", "format": "at-uri" }, 10 + "cid": { "type": "string", "format": "cid" }, 11 + "author": { 12 + "type": "ref", 13 + "ref": "social.grain.actor.defs#profileView" 14 + }, 15 + "record": { "type": "unknown" }, 16 + "text": { 17 + "type": "string", 18 + "maxLength": 3000, 19 + "maxGraphemes": 300 20 + }, 21 + "facets": { 22 + "type": "array", 23 + "description": "Annotations of description text (mentions and URLs, hashtags, etc)", 24 + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } 25 + }, 26 + "subject": { 27 + "type": "union", 28 + "refs": [ 29 + "social.grain.gallery.defs#galleryView" 30 + ], 31 + "description": "The subject of the comment, which can be a gallery or a photo." 32 + }, 33 + "focus": { 34 + "type": "union", 35 + "refs": [ 36 + "social.grain.photo.defs#photoView" 37 + ], 38 + "description": "The photo that the comment is focused on, if applicable." 39 + }, 40 + "replyTo": { 41 + "type": "string", 42 + "format": "at-uri", 43 + "description": "The URI of the comment this comment is replying to, if applicable." 44 + }, 45 + "createdAt": { 46 + "type": "string", 47 + "format": "datetime" 48 + } 49 + } 50 + } 51 + } 52 + }
+36
lexicons/social/grain/comment/deleteComment.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.comment.deleteComment", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a comment. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["uri"], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "AT URI of the comment to delete" 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "properties": { 27 + "success": { 28 + "type": "boolean", 29 + "description": "True if the comment was deleted" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+15
lexicons/social/grain/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.defs", 4 + "defs": { 5 + "aspectRatio": { 6 + "type": "object", 7 + "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.", 8 + "required": ["width", "height"], 9 + "properties": { 10 + "width": { "type": "integer", "minimum": 1 }, 11 + "height": { "type": "integer", "minimum": 1 } 12 + } 13 + } 14 + } 15 + }
+38
lexicons/social/grain/favorite/createFavorite.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.favorite.createFavorite", 4 + "defs": { 5 + "main": { 6 + "description": "Create a favorite for a given subject.", 7 + "type": "procedure", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["subject"], 13 + "properties": { 14 + "subject": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "URI of the subject to favorite." 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["favoriteUri"], 27 + "properties": { 28 + "favoriteUri": { 29 + "type": "string", 30 + "format": "at-uri", 31 + "description": "AT URI for the created favorite." 32 + } 33 + } 34 + } 35 + } 36 + } 37 + } 38 + }
+37
lexicons/social/grain/favorite/deleteFavorite.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.favorite.deleteFavorite", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a favorite item by its ID.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["uri"], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "The AT URI of the favorite to delete." 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["success"], 27 + "properties": { 28 + "success": { 29 + "type": "boolean", 30 + "description": "Indicates if the favorite was successfully deleted." 31 + } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+24
lexicons/social/grain/favorite/favorite.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.favorite", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["createdAt", "subject"], 11 + "properties": { 12 + "createdAt": { 13 + "type": "string", 14 + "format": "datetime" 15 + }, 16 + "subject": { 17 + "type": "string", 18 + "format": "at-uri" 19 + } 20 + } 21 + } 22 + } 23 + } 24 + }
+43
lexicons/social/grain/feed/getTimeline.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.feed.getTimeline", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a view of the requesting account's home timeline.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "algorithm": { 12 + "type": "string", 13 + "description": "Variant 'algorithm' for timeline. Implementation-specific." 14 + }, 15 + "limit": { 16 + "type": "integer", 17 + "minimum": 1, 18 + "maximum": 100, 19 + "default": 50 20 + }, 21 + "cursor": { "type": "string" } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "application/json", 26 + "schema": { 27 + "type": "object", 28 + "required": ["feed"], 29 + "properties": { 30 + "cursor": { "type": "string" }, 31 + "feed": { 32 + "type": "array", 33 + "items": { 34 + "type": "ref", 35 + "ref": "social.grain.gallery.defs#galleryView" 36 + } 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + }
+58
lexicons/social/grain/gallery/applySort.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.applySort", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Apply sorting to photos in a gallery. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["galleryUri", "writes"], 13 + "properties": { 14 + "galleryUri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "The AT-URI of the gallery to update" 18 + }, 19 + "writes": { 20 + "type": "array", 21 + "items": { 22 + "type": "ref", 23 + "ref": "#update" 24 + } 25 + } 26 + } 27 + } 28 + }, 29 + "output": { 30 + "encoding": "application/json", 31 + "schema": { 32 + "type": "object", 33 + "properties": { 34 + "success": { 35 + "type": "boolean", 36 + "description": "True if the writes were successfully applied" 37 + } 38 + } 39 + } 40 + } 41 + }, 42 + "update": { 43 + "type": "object", 44 + "required": ["itemUri", "position"], 45 + "properties": { 46 + "itemUri": { 47 + "type": "string", 48 + "format": "at-uri", 49 + "description": "AT URI of the item to update" 50 + }, 51 + "position": { 52 + "type": "integer", 53 + "description": "The position of the item in the gallery, used for ordering" 54 + } 55 + } 56 + } 57 + } 58 + }
+34
lexicons/social/grain/gallery/createGallery.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.createGallery", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a new gallery", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["title"], 13 + "properties": { 14 + "title": { "type": "string", "maxLength": 100 }, 15 + "description": { "type": "string", "maxLength": 1000 } 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "properties": { 24 + "galleryUri": { 25 + "type": "string", 26 + "format": "at-uri", 27 + "description": "AT URI of the created gallery" 28 + } 29 + } 30 + } 31 + } 32 + } 33 + } 34 + }
+46
lexicons/social/grain/gallery/createItem.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.createItem", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a new gallery item", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["galleryUri", "photoUri", "position"], 13 + "properties": { 14 + "galleryUri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "AT URI of the gallery to create the item in" 18 + }, 19 + "photoUri": { 20 + "type": "string", 21 + "format": "at-uri", 22 + "description": "AT URI of the photo to be added as an item" 23 + }, 24 + "position": { 25 + "type": "integer", 26 + "description": "Position of the item in the gallery, used for ordering" 27 + } 28 + } 29 + } 30 + }, 31 + "output": { 32 + "encoding": "application/json", 33 + "schema": { 34 + "type": "object", 35 + "properties": { 36 + "itemUri": { 37 + "type": "string", 38 + "format": "at-uri", 39 + "description": "AT URI of the created gallery item" 40 + } 41 + } 42 + } 43 + } 44 + } 45 + } 46 + }
+56
lexicons/social/grain/gallery/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.defs", 4 + "defs": { 5 + "galleryView": { 6 + "type": "object", 7 + "required": ["uri", "cid", "creator", "record", "indexedAt"], 8 + "properties": { 9 + "uri": { "type": "string", "format": "at-uri" }, 10 + "cid": { "type": "string", "format": "cid" }, 11 + "title": { "type": "string" }, 12 + "description": { "type": "string" }, 13 + "cameras": { 14 + "type": "array", 15 + "description": "List of camera make and models used in this gallery derived from EXIF data.", 16 + "items": { "type": "string" } 17 + }, 18 + "facets": { 19 + "type": "array", 20 + "description": "Annotations of description text (mentions, URLs, hashtags, etc)", 21 + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } 22 + }, 23 + "creator": { 24 + "type": "ref", 25 + "ref": "social.grain.actor.defs#profileView" 26 + }, 27 + "record": { "type": "unknown" }, 28 + "items": { 29 + "type": "array", 30 + "items": { 31 + "type": "union", 32 + "refs": [ 33 + "social.grain.photo.defs#photoView" 34 + ] 35 + } 36 + }, 37 + "favCount": { "type": "integer" }, 38 + "commentCount": { "type": "integer" }, 39 + "labels": { 40 + "type": "array", 41 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 42 + }, 43 + "createdAt": { "type": "string", "format": "datetime" }, 44 + "indexedAt": { "type": "string", "format": "datetime" }, 45 + "viewer": { "type": "ref", "ref": "#viewerState" } 46 + } 47 + }, 48 + "viewerState": { 49 + "type": "object", 50 + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", 51 + "properties": { 52 + "fav": { "type": "string", "format": "at-uri" } 53 + } 54 + } 55 + } 56 + }
+36
lexicons/social/grain/gallery/deleteGallery.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.deleteGallery", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a gallery. Does not delete the items in the gallery, just the gallery itself.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["uri"], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "Unique identifier of the gallery to delete" 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "properties": { 27 + "success": { 28 + "type": "boolean", 29 + "description": "True if the gallery was deleted" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+36
lexicons/social/grain/gallery/deleteItem.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.deleteItem", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a gallery item", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["uri"], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "AT URI of the gallery to create the item in" 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "properties": { 27 + "success": { 28 + "type": "boolean", 29 + "description": "True if the gallery item was deleted" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+30
lexicons/social/grain/gallery/gallery.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["title", "createdAt"], 11 + "properties": { 12 + "title": { "type": "string", "maxLength": 100 }, 13 + "description": { "type": "string", "maxLength": 1000 }, 14 + "facets": { 15 + "type": "array", 16 + "description": "Annotations of description text (mentions, URLs, hashtags, etc)", 17 + "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } 18 + }, 19 + "labels": { 20 + "type": "union", 21 + "description": "Self-label values for this post. Effectively content warnings.", 22 + "refs": ["com.atproto.label.defs#selfLabels"] 23 + }, 24 + "updatedAt": { "type": "string", "format": "datetime" }, 25 + "createdAt": { "type": "string", "format": "datetime" } 26 + } 27 + } 28 + } 29 + } 30 + }
+41
lexicons/social/grain/gallery/getActorGalleries.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.getActorGalleries", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a view of an actor's galleries. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { "type": "string", "format": "at-identifier" }, 13 + "limit": { 14 + "type": "integer", 15 + "minimum": 1, 16 + "maximum": 100, 17 + "default": 50 18 + }, 19 + "cursor": { "type": "string" } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["items"], 27 + "properties": { 28 + "cursor": { "type": "string" }, 29 + "items": { 30 + "type": "array", 31 + "items": { 32 + "type": "ref", 33 + "ref": "social.grain.gallery.defs#galleryView" 34 + } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + } 41 + }
+28
lexicons/social/grain/gallery/getGallery.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.getGallery", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Gets a hydrated gallery view for a specified gallery AT-URI.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["uri"], 11 + "properties": { 12 + "uri": { 13 + "type": "string", 14 + "description": "The AT-URI of the gallery to return a hydrated view for.", 15 + "format": "at-uri" 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "ref", 23 + "ref": "social.grain.gallery.defs#galleryView" 24 + } 25 + } 26 + } 27 + } 28 + }
+41
lexicons/social/grain/gallery/getGalleryThread.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.getGalleryThread", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Gets a hydrated gallery view and its comments for a specified gallery AT-URI.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["uri"], 11 + "properties": { 12 + "uri": { 13 + "type": "string", 14 + "description": "The AT-URI of the gallery to return a hydrated view and comments for.", 15 + "format": "at-uri" 16 + } 17 + } 18 + }, 19 + "output": { 20 + "encoding": "application/json", 21 + "schema": { 22 + "type": "object", 23 + "required": ["gallery", "comments"], 24 + "properties": { 25 + "gallery": { 26 + "type": "ref", 27 + "ref": "social.grain.gallery.defs#galleryView" 28 + }, 29 + "comments": { 30 + "type": "array", 31 + "items": { 32 + "type": "ref", 33 + "ref": "social.grain.comment.defs#commentView" 34 + } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + } 41 + }
+32
lexicons/social/grain/gallery/item.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.item", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["createdAt", "gallery", "item"], 11 + "properties": { 12 + "createdAt": { 13 + "type": "string", 14 + "format": "datetime" 15 + }, 16 + "gallery": { 17 + "type": "string", 18 + "format": "at-uri" 19 + }, 20 + "item": { 21 + "type": "string", 22 + "format": "at-uri" 23 + }, 24 + "position": { 25 + "type": "integer", 26 + "default": 0 27 + } 28 + } 29 + } 30 + } 31 + } 32 + }
+38
lexicons/social/grain/gallery/updateGallery.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.gallery.updateGallery", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a new gallery", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["galleryUri", "title"], 13 + "properties": { 14 + "galleryUri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "The AT-URI of the gallery to update" 18 + }, 19 + "title": { "type": "string" }, 20 + "description": { "type": "string" } 21 + } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "application/json", 26 + "schema": { 27 + "type": "object", 28 + "properties": { 29 + "success": { 30 + "type": "boolean", 31 + "description": "True if the gallery was updated" 32 + } 33 + } 34 + } 35 + } 36 + } 37 + } 38 + }
+37
lexicons/social/grain/graph/createFollow.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.graph.createFollow", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a follow relationship between actors.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["subject"], 13 + "properties": { 14 + "subject": { 15 + "type": "string", 16 + "format": "at-identifier", 17 + "description": "DID of the actor to follow." 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "properties": { 27 + "followUri": { 28 + "type": "string", 29 + "format": "at-uri", 30 + "description": "AT URI of the created follow record." 31 + } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+36
lexicons/social/grain/graph/deleteFollow.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.graph.deleteFollow", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a follow relationship. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["uri"], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "AT URI of the follow record to delete" 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "properties": { 27 + "success": { 28 + "type": "boolean", 29 + "description": "True if the follow was deleted" 30 + } 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+27
lexicons/social/grain/graph/follow.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.graph.follow", 4 + "defs": { 5 + "main": { 6 + "key": "tid", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "subject", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "subject": { 16 + "type": "string", 17 + "format": "did" 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + } 23 + } 24 + } 25 + } 26 + } 27 + }
+45
lexicons/social/grain/graph/getFollowers.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.graph.getFollowers", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Enumerates accounts which follow a specified account (actor).", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { "type": "string", "format": "at-identifier" }, 13 + "limit": { 14 + "type": "integer", 15 + "minimum": 1, 16 + "maximum": 100, 17 + "default": 50 18 + }, 19 + "cursor": { "type": "string" } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["subject", "followers"], 27 + "properties": { 28 + "subject": { 29 + "type": "ref", 30 + "ref": "social.grain.actor.defs#profileView" 31 + }, 32 + "cursor": { "type": "string" }, 33 + "followers": { 34 + "type": "array", 35 + "items": { 36 + "type": "ref", 37 + "ref": "social.grain.actor.defs#profileView" 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + } 45 + }
+45
lexicons/social/grain/graph/getFollows.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.graph.getFollows", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Enumerates accounts which a specified account (actor) follows.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { "type": "string", "format": "at-identifier" }, 13 + "limit": { 14 + "type": "integer", 15 + "minimum": 1, 16 + "maximum": 100, 17 + "default": 50 18 + }, 19 + "cursor": { "type": "string" } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["subject", "follows"], 27 + "properties": { 28 + "subject": { 29 + "type": "ref", 30 + "ref": "social.grain.actor.defs#profileView" 31 + }, 32 + "cursor": { "type": "string" }, 33 + "follows": { 34 + "type": "array", 35 + "items": { 36 + "type": "ref", 37 + "ref": "social.grain.actor.defs#profileView" 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + } 45 + }
+94
lexicons/social/grain/labelers/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.labeler.defs", 4 + "defs": { 5 + "labelerView": { 6 + "type": "object", 7 + "required": ["uri", "cid", "creator", "indexedAt"], 8 + "properties": { 9 + "uri": { "type": "string", "format": "at-uri" }, 10 + "cid": { "type": "string", "format": "cid" }, 11 + "creator": { 12 + "type": "ref", 13 + "ref": "social.grain.actor.defs#profileView" 14 + }, 15 + "favoriteCount": { "type": "integer", "minimum": 0 }, 16 + "viewer": { "type": "ref", "ref": "#labelerViewerState" }, 17 + "indexedAt": { "type": "string", "format": "datetime" }, 18 + "labels": { 19 + "type": "array", 20 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 21 + } 22 + } 23 + }, 24 + "labelerViewDetailed": { 25 + "type": "object", 26 + "required": ["uri", "cid", "creator", "policies", "indexedAt"], 27 + "properties": { 28 + "uri": { "type": "string", "format": "at-uri" }, 29 + "cid": { "type": "string", "format": "cid" }, 30 + "creator": { "type": "ref", "ref": "app.bsky.actor.defs#profileView" }, 31 + "policies": { 32 + "type": "ref", 33 + "ref": "social.grain.actor.defs#labelerPolicies" 34 + }, 35 + "favoriteCount": { "type": "integer", "minimum": 0 }, 36 + "viewer": { "type": "ref", "ref": "#labelerViewerState" }, 37 + "indexedAt": { "type": "string", "format": "datetime" }, 38 + "labels": { 39 + "type": "array", 40 + "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } 41 + }, 42 + "reasonTypes": { 43 + "description": "The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed.", 44 + "type": "array", 45 + "items": { 46 + "type": "ref", 47 + "ref": "com.atproto.moderation.defs#reasonType" 48 + } 49 + }, 50 + "subjectTypes": { 51 + "description": "The set of subject types (account, record, etc) this service accepts reports on.", 52 + "type": "array", 53 + "items": { 54 + "type": "ref", 55 + "ref": "com.atproto.moderation.defs#subjectType" 56 + } 57 + }, 58 + "subjectCollections": { 59 + "type": "array", 60 + "description": "Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type.", 61 + "items": { "type": "string", "format": "nsid" } 62 + } 63 + } 64 + }, 65 + "labelerViewerState": { 66 + "type": "object", 67 + "properties": { 68 + "like": { "type": "string", "format": "at-uri" } 69 + } 70 + }, 71 + "labelerPolicies": { 72 + "type": "object", 73 + "required": ["labelValues"], 74 + "properties": { 75 + "labelValues": { 76 + "type": "array", 77 + "description": "The label values which this labeler publishes. May include global or custom labels.", 78 + "items": { 79 + "type": "ref", 80 + "ref": "com.atproto.label.defs#labelValue" 81 + } 82 + }, 83 + "labelValueDefinitions": { 84 + "type": "array", 85 + "description": "Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler.", 86 + "items": { 87 + "type": "ref", 88 + "ref": "com.atproto.label.defs#labelValueDefinition" 89 + } 90 + } 91 + } 92 + } 93 + } 94 + }
+47
lexicons/social/grain/labelers/service.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.labeler.service", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of the existence of labeler service.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "required": ["policies", "createdAt"], 12 + "properties": { 13 + "policies": { 14 + "type": "ref", 15 + "ref": "app.bsky.labeler.defs#labelerPolicies" 16 + }, 17 + "labels": { 18 + "type": "union", 19 + "refs": ["com.atproto.label.defs#selfLabels"] 20 + }, 21 + "createdAt": { "type": "string", "format": "datetime" }, 22 + "reasonTypes": { 23 + "description": "The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed.", 24 + "type": "array", 25 + "items": { 26 + "type": "ref", 27 + "ref": "com.atproto.moderation.defs#reasonType" 28 + } 29 + }, 30 + "subjectTypes": { 31 + "description": "The set of subject types (account, record, etc) this service accepts reports on.", 32 + "type": "array", 33 + "items": { 34 + "type": "ref", 35 + "ref": "com.atproto.moderation.defs#subjectType" 36 + } 37 + }, 38 + "subjectCollections": { 39 + "type": "array", 40 + "description": "Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type.", 41 + "items": { "type": "string", "format": "nsid" } 42 + } 43 + } 44 + } 45 + } 46 + } 47 + }
+87
lexicons/social/grain/notification/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.notification.defs", 4 + "defs": { 5 + "notificationView": { 6 + "type": "object", 7 + "required": [ 8 + "uri", 9 + "cid", 10 + "author", 11 + "reason", 12 + "record", 13 + "isRead", 14 + "indexedAt" 15 + ], 16 + "properties": { 17 + "uri": { "type": "string", "format": "at-uri" }, 18 + "cid": { "type": "string", "format": "cid" }, 19 + "author": { 20 + "type": "ref", 21 + "ref": "social.grain.actor.defs#profileView" 22 + }, 23 + "reasonSubject": { "type": "string", "format": "at-uri" }, 24 + "reason": { 25 + "type": "string", 26 + "description": "The reason why this notification was delivered - e.g. your gallery was favd, or you received a new follower.", 27 + "knownValues": [ 28 + "follow", 29 + "gallery-favorite", 30 + "gallery-comment", 31 + "reply", 32 + "gallery-mention", 33 + "gallery-comment-mention", 34 + "unknown" 35 + ] 36 + }, 37 + "record": { "type": "unknown" }, 38 + "isRead": { "type": "boolean" }, 39 + "indexedAt": { "type": "string", "format": "datetime" } 40 + } 41 + }, 42 + "notificationViewDetailed": { 43 + "type": "object", 44 + "required": [ 45 + "uri", 46 + "cid", 47 + "author", 48 + "reason", 49 + "record", 50 + "isRead", 51 + "indexedAt" 52 + ], 53 + "properties": { 54 + "uri": { "type": "string", "format": "at-uri" }, 55 + "cid": { "type": "string", "format": "cid" }, 56 + "author": { 57 + "type": "ref", 58 + "ref": "social.grain.actor.defs#profileView" 59 + }, 60 + "reason": { 61 + "type": "string", 62 + "description": "The reason why this notification was delivered - e.g. your gallery was favd, or you received a new follower.", 63 + "knownValues": [ 64 + "follow", 65 + "gallery-favorite", 66 + "gallery-comment", 67 + "reply", 68 + "gallery-mention", 69 + "gallery-comment-mention", 70 + "unknown" 71 + ] 72 + }, 73 + "reasonSubject": { 74 + "type": "union", 75 + "refs": [ 76 + "social.grain.actor.defs#profileView", 77 + "social.grain.comment.defs#commentView", 78 + "social.grain.gallery.defs#galleryView" 79 + ] 80 + }, 81 + "record": { "type": "unknown" }, 82 + "isRead": { "type": "boolean" }, 83 + "indexedAt": { "type": "string", "format": "datetime" } 84 + } 85 + } 86 + } 87 + }
+40
lexicons/social/grain/notification/getNotifications.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.notification.getNotifications", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Enumerate notifications for the requesting account. Requires auth.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "limit": { 12 + "type": "integer", 13 + "minimum": 1, 14 + "maximum": 100, 15 + "default": 50 16 + }, 17 + "cursor": { "type": "string" } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["notifications"], 25 + "properties": { 26 + "cursor": { "type": "string" }, 27 + "notifications": { 28 + "type": "array", 29 + "items": { 30 + "type": "ref", 31 + "ref": "social.grain.notification.defs#notificationViewDetailed" 32 + } 33 + }, 34 + "seenAt": { "type": "string", "format": "datetime" } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + }
+20
lexicons/social/grain/notification/updateSeen.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.notification.updateSeen", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Notify server that the requesting account has seen notifications. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["seenAt"], 13 + "properties": { 14 + "seenAt": { "type": "string", "format": "datetime" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+54
lexicons/social/grain/photo/applyAlts.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo.applyAlts", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Apply alt texts to photos in a gallery. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["writes"], 13 + "properties": { 14 + "writes": { 15 + "type": "array", 16 + "items": { 17 + "type": "ref", 18 + "ref": "#update" 19 + } 20 + } 21 + } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "application/json", 26 + "schema": { 27 + "type": "object", 28 + "properties": { 29 + "success": { 30 + "type": "boolean", 31 + "description": "True if the writes were successfully applied" 32 + } 33 + } 34 + } 35 + } 36 + }, 37 + "update": { 38 + "type": "object", 39 + "required": ["photoUri", "alt"], 40 + "properties": { 41 + "photoUri": { 42 + "type": "string", 43 + "format": "at-uri", 44 + "description": "AT URI of the item to update" 45 + }, 46 + "alt": { 47 + "type": "string", 48 + "maxLength": 1000, 49 + "description": "The alt text to apply to the photo" 50 + } 51 + } 52 + } 53 + } 54 + }
+44
lexicons/social/grain/photo/createExif.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo.createExif", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a new Exif record for a photo", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["photo", "createdAt"], 13 + "properties": { 14 + "photo": { "type": "string", "format": "at-uri" }, 15 + "createdAt": { "type": "string", "format": "datetime" }, 16 + "dateTimeOriginal": { "type": "string", "format": "datetime" }, 17 + "exposureTime": { "type": "integer" }, 18 + "fNumber": { "type": "integer" }, 19 + "flash": { "type": "string" }, 20 + "focalLengthIn35mmFormat": { "type": "integer" }, 21 + "iSO": { "type": "integer" }, 22 + "lensMake": { "type": "string" }, 23 + "lensModel": { "type": "string" }, 24 + "make": { "type": "string" }, 25 + "model": { "type": "string" } 26 + } 27 + } 28 + }, 29 + "output": { 30 + "encoding": "application/json", 31 + "schema": { 32 + "type": "object", 33 + "properties": { 34 + "exifUri": { 35 + "type": "string", 36 + "format": "at-uri", 37 + "description": "AT URI of the created gallery" 38 + } 39 + } 40 + } 41 + } 42 + } 43 + } 44 + }
+26
lexicons/social/grain/photo/createPhoto.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo.createPhoto", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Create a photo. Requires auth.", 8 + "input": { 9 + "encoding": "*/*" 10 + }, 11 + "output": { 12 + "encoding": "application/json", 13 + "schema": { 14 + "type": "object", 15 + "properties": { 16 + "photoUri": { 17 + "type": "string", 18 + "format": "at-uri", 19 + "description": "AT URI of the created photo" 20 + } 21 + } 22 + } 23 + } 24 + } 25 + } 26 + }
+68
lexicons/social/grain/photo/defs.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo.defs", 4 + "defs": { 5 + "photoView": { 6 + "type": "object", 7 + "required": ["uri", "cid", "thumb", "fullsize", "alt"], 8 + "properties": { 9 + "uri": { "type": "string", "format": "at-uri" }, 10 + "cid": { "type": "string", "format": "cid" }, 11 + "thumb": { 12 + "type": "string", 13 + "format": "uri", 14 + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." 15 + }, 16 + "fullsize": { 17 + "type": "string", 18 + "format": "uri", 19 + "description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View." 20 + }, 21 + "alt": { 22 + "type": "string", 23 + "description": "Alt text description of the image, for accessibility." 24 + }, 25 + "aspectRatio": { 26 + "type": "ref", 27 + "ref": "social.grain.defs#aspectRatio" 28 + }, 29 + "exif": { 30 + "type": "ref", 31 + "ref": "social.grain.photo.defs#exifView", 32 + "description": "EXIF metadata for the photo, if available." 33 + }, 34 + "gallery": { "type": "ref", "ref": "#galleryState" } 35 + } 36 + }, 37 + "exifView": { 38 + "type": "object", 39 + "required": ["photo", "createdAt"], 40 + "properties": { 41 + "uri": { "type": "string", "format": "at-uri" }, 42 + "cid": { "type": "string", "format": "cid" }, 43 + "photo": { "type": "string", "format": "at-uri" }, 44 + "createdAt": { "type": "string", "format": "datetime" }, 45 + "dateTimeOriginal": { "type": "string" }, 46 + "exposureTime": { "type": "string" }, 47 + "fNumber": { "type": "string" }, 48 + "flash": { "type": "string" }, 49 + "focalLengthIn35mmFormat": { "type": "string" }, 50 + "iSO": { "type": "integer" }, 51 + "lensMake": { "type": "string" }, 52 + "lensModel": { "type": "string" }, 53 + "make": { "type": "string" }, 54 + "model": { "type": "string" } 55 + } 56 + }, 57 + "galleryState": { 58 + "type": "object", 59 + "required": ["item", "itemCreatedAt", "itemPosition"], 60 + "description": "Metadata about the photo's relationship with the subject content. Only has meaningful content when photo is attached to a gallery.", 61 + "properties": { 62 + "item": { "type": "string", "format": "at-uri" }, 63 + "itemCreatedAt": { "type": "string", "format": "datetime" }, 64 + "itemPosition": { "type": "integer" } 65 + } 66 + } 67 + } 68 + }
+37
lexicons/social/grain/photo/deletePhoto.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo.deletePhoto", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Delete a favorite photo by its unique at-uri.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["uri"], 13 + "properties": { 14 + "uri": { 15 + "type": "string", 16 + "format": "at-uri", 17 + "description": "AT URI of the photo to delete." 18 + } 19 + } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["success"], 27 + "properties": { 28 + "success": { 29 + "type": "boolean", 30 + "description": "Indicates if the photo was successfully deleted." 31 + } 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+32
lexicons/social/grain/photo/exif.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo.exif", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Basic EXIF metadata for a photo. Integers are scaled by 1000000 to accommodate decimal values and potentially other tags in the future.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "photo", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "photo": { "type": "string", "format": "at-uri" }, 17 + "createdAt": { "type": "string", "format": "datetime" }, 18 + "dateTimeOriginal": { "type": "string", "format": "datetime" }, 19 + "exposureTime": { "type": "integer" }, 20 + "fNumber": { "type": "integer" }, 21 + "flash": { "type": "string" }, 22 + "focalLengthIn35mmFormat": { "type": "integer" }, 23 + "iSO": { "type": "integer" }, 24 + "lensMake": { "type": "string" }, 25 + "lensModel": { "type": "string" }, 26 + "make": { "type": "string" }, 27 + "model": { "type": "string" } 28 + } 29 + } 30 + } 31 + } 32 + }
+41
lexicons/social/grain/photo/getActorPhotos.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo.getActorPhotos", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get a view of an actor's photos. Does not require auth.", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor"], 11 + "properties": { 12 + "actor": { "type": "string", "format": "at-identifier" }, 13 + "limit": { 14 + "type": "integer", 15 + "minimum": 1, 16 + "maximum": 100, 17 + "default": 50 18 + }, 19 + "cursor": { "type": "string" } 20 + } 21 + }, 22 + "output": { 23 + "encoding": "application/json", 24 + "schema": { 25 + "type": "object", 26 + "required": ["items"], 27 + "properties": { 28 + "cursor": { "type": "string" }, 29 + "items": { 30 + "type": "array", 31 + "items": { 32 + "type": "ref", 33 + "ref": "social.grain.photo.defs#photoView" 34 + } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + } 41 + }
+30
lexicons/social/grain/photo/photo.json
···
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.photo", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["photo", "alt"], 11 + "properties": { 12 + "photo": { 13 + "type": "blob", 14 + "accept": ["image/*"], 15 + "maxSize": 1000000 16 + }, 17 + "alt": { 18 + "type": "string", 19 + "description": "Alt text description of the image, for accessibility." 20 + }, 21 + "aspectRatio": { 22 + "type": "ref", 23 + "ref": "social.grain.defs#aspectRatio" 24 + }, 25 + "createdAt": { "type": "string", "format": "datetime" } 26 + } 27 + } 28 + } 29 + } 30 + }
+397 -547
lib/api.dart
··· 1 import 'dart:convert'; 2 import 'dart:io'; 3 4 - import 'package:at_uri/at_uri.dart'; 5 - import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 6 import 'package:grain/app_logger.dart'; 7 - import 'package:grain/dpop_client.dart'; 8 import 'package:grain/main.dart'; 9 - import 'package:grain/models/atproto_session.dart'; 10 - import 'package:grain/photo_manip.dart'; 11 import 'package:http/http.dart' as http; 12 - import 'package:jose/jose.dart'; 13 import 'package:mime/mime.dart'; 14 15 import './auth.dart'; 16 import 'models/followers_result.dart'; 17 import 'models/follows_result.dart'; 18 import 'models/gallery.dart'; 19 - import 'models/gallery_item.dart'; 20 import 'models/gallery_photo.dart'; 21 import 'models/gallery_thread.dart'; 22 import 'models/notification.dart' as grain; 23 import 'models/profile.dart'; 24 25 class ApiService { 26 - // ...existing code... 27 - static const _storage = FlutterSecureStorage(); 28 - String? _accessToken; 29 Profile? currentUser; 30 Profile? loadedProfile; 31 List<Gallery> galleries = []; 32 33 String get _apiUrl => AppConfig.apiUrl; 34 35 - Future<void> loadToken() async { 36 - _accessToken = await _storage.read(key: 'access_token'); 37 - } 38 - 39 - Future<void> setToken(String? token) async { 40 - _accessToken = token; 41 - if (token != null) { 42 - await _storage.write(key: 'access_token', value: token); 43 - } else { 44 - await _storage.delete(key: 'access_token'); 45 } 46 } 47 48 - bool get hasToken => _accessToken != null && _accessToken!.isNotEmpty; 49 - 50 - Future<AtprotoSession?> fetchSession() async { 51 - if (_accessToken == null) return null; 52 - 53 - final response = await http.get( 54 - Uri.parse('$_apiUrl/oauth/session'), 55 - headers: {'Authorization': 'Bearer $_accessToken', 'Content-Type': 'application/json'}, 56 - ); 57 - 58 - if (response.statusCode != 200) { 59 - throw Exception('Failed to fetch session'); 60 } 61 - 62 - final json = jsonDecode(response.body); 63 - final token = json['tokenSet'] ?? {}; 64 - return AtprotoSession( 65 - accessToken: token['access_token'] as String, 66 - tokenType: token['token_type'] as String, 67 - expiresAt: DateTime.parse(token['expires_at'] as String), 68 - dpopJwk: JsonWebKey.fromJson(json['dpopJwk'] as Map<String, dynamic>), 69 - issuer: token['iss'] as String, 70 - subject: token['sub'] as String, 71 - ); 72 } 73 74 Future<Profile?> fetchCurrentUser() async { 75 final session = await auth.getValidSession(); 76 77 - if (session == null || session.subject.isEmpty) { 78 return null; 79 } 80 81 - final user = await fetchProfile(did: session.subject); 82 83 currentUser = user; 84 ··· 86 } 87 88 Future<Profile?> fetchProfile({required String did}) async { 89 appLogger.i('Fetching profile for did: $did'); 90 final response = await http.get( 91 Uri.parse('$_apiUrl/xrpc/social.grain.actor.getProfile?actor=$did'), 92 - headers: {'Content-Type': 'application/json', 'Authorization': "Bearer $_accessToken"}, 93 ); 94 if (response.statusCode != 200) { 95 appLogger.w('Failed to fetch profile: ${response.statusCode} ${response.body}'); ··· 130 } 131 132 Future<List<Gallery>> getTimeline({String? algorithm}) async { 133 - if (_accessToken == null) { 134 return []; 135 } 136 - appLogger.i('Fetching timeline with algorithm: \\${algorithm ?? 'default'}'); 137 final uri = algorithm != null 138 ? Uri.parse('$_apiUrl/xrpc/social.grain.feed.getTimeline?algorithm=$algorithm') 139 : Uri.parse('$_apiUrl/xrpc/social.grain.feed.getTimeline'); 140 final response = await http.get( 141 uri, 142 - headers: {'Authorization': "Bearer $_accessToken", 'Content-Type': 'application/json'}, 143 ); 144 if (response.statusCode != 200) { 145 appLogger.w('Failed to fetch timeline: ${response.statusCode} ${response.body}'); ··· 154 155 Future<Gallery?> getGallery({required String uri}) async { 156 appLogger.i('Fetching gallery for uri: $uri'); 157 final response = await http.get( 158 Uri.parse('$_apiUrl/xrpc/social.grain.gallery.getGallery?uri=$uri'), 159 - headers: {'Authorization': "Bearer $_accessToken", 'Content-Type': 'application/json'}, 160 ); 161 if (response.statusCode != 200) { 162 appLogger.w('Failed to fetch gallery: ${response.statusCode} ${response.body}'); ··· 178 179 Future<GalleryThread?> getGalleryThread({required String uri}) async { 180 appLogger.i('Fetching gallery thread for uri: $uri'); 181 final response = await http.get( 182 Uri.parse('$_apiUrl/xrpc/social.grain.gallery.getGalleryThread?uri=$uri'), 183 - headers: {'Content-Type': 'application/json', 'Authorization': "Bearer $_accessToken"}, 184 ); 185 if (response.statusCode != 200) { 186 - appLogger.w('Failed to fetch gallery thread: \\${response.statusCode} \\${response.body}'); 187 return null; 188 } 189 final json = jsonDecode(response.body) as Map<String, dynamic>; ··· 191 } 192 193 Future<List<grain.Notification>> getNotifications() async { 194 - if (_accessToken == null) { 195 appLogger.w('No access token for getNotifications'); 196 return []; 197 } 198 appLogger.i('Fetching notifications'); 199 final response = await http.get( 200 Uri.parse('$_apiUrl/xrpc/social.grain.notification.getNotifications'), 201 - headers: {'Authorization': "Bearer $_accessToken", 'Content-Type': 'application/json'}, 202 ); 203 if (response.statusCode != 200) { 204 - appLogger.w('Failed to fetch notifications: \\${response.statusCode} \\${response.body}'); 205 return []; 206 } 207 final json = jsonDecode(response.body); ··· 238 } 239 240 Future<List<Profile>> searchActors(String query) async { 241 - if (_accessToken == null) { 242 appLogger.w('No access token for searchActors'); 243 return []; 244 } 245 appLogger.i('Searching actors with query: $query'); 246 final response = await http.get( 247 Uri.parse('$_apiUrl/xrpc/social.grain.actor.searchActors?q=$query'), 248 - headers: {'Authorization': "Bearer $_accessToken", 'Content-Type': 'application/json'}, 249 ); 250 if (response.statusCode != 200) { 251 appLogger.w('Failed to search actors: ${response.statusCode} ${response.body}'); ··· 269 return (json['items'] as List<dynamic>?)?.map((item) => Gallery.fromJson(item)).toList() ?? []; 270 } 271 272 - Future<String?> createGallery({ 273 - required String title, 274 - required String description, 275 - List<Map<String, dynamic>>? facets, 276 }) async { 277 final session = await auth.getValidSession(); 278 - if (session == null) { 279 - appLogger.w('No valid session for createGallery'); 280 - return null; 281 } 282 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 283 - final issuer = session.issuer; 284 - final did = session.subject; 285 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 286 - final record = { 287 - 'collection': 'social.grain.gallery', 288 - 'repo': did, 289 - 'record': { 290 - 'title': title, 291 - 'description': description, 292 - if (facets != null) 'facets': facets, 293 - 'updatedAt': DateTime.now().toUtc().toIso8601String(), 294 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 295 - }, 296 - }; 297 - appLogger.i('Creating gallery: $record'); 298 - final response = await dpopClient.send( 299 - method: 'POST', 300 - url: url, 301 - accessToken: session.accessToken, 302 - headers: {'Content-Type': 'application/json'}, 303 - body: jsonEncode(record), 304 ); 305 - if (response.statusCode != 200 && response.statusCode != 201) { 306 - appLogger.w('Failed to create gallery: \\${response.statusCode} \\${response.body}'); 307 - throw Exception('Failed to create gallery: \\${response.statusCode}'); 308 } 309 - final result = jsonDecode(response.body) as Map<String, dynamic>; 310 - appLogger.i('Created gallery result: $result'); 311 - final uri = result['uri'] as String?; 312 - return uri; 313 } 314 315 - /// Polls the gallery until the number of items matches [expectedCount] or timeout. 316 - /// Returns the Gallery if successful, or null if timeout. 317 - Future<Gallery?> pollGalleryItems({ 318 - required String galleryUri, 319 - required int expectedCount, 320 - Duration pollDelay = const Duration(seconds: 2), 321 - int maxAttempts = 20, 322 - }) async { 323 - int attempts = 0; 324 - Gallery? gallery; 325 - while (attempts < maxAttempts) { 326 - gallery = await getGallery(uri: galleryUri); 327 - if (gallery != null && gallery.items.length == expectedCount) { 328 - appLogger.i('Gallery $galleryUri has expected number of items: $expectedCount'); 329 - return gallery; 330 - } 331 - await Future.delayed(pollDelay); 332 - attempts++; 333 } 334 - appLogger.w( 335 - 'Gallery $galleryUri did not reach expected items count ($expectedCount) after polling.', 336 ); 337 - return null; 338 } 339 340 - /// Polls the gallery thread until the number of comments matches [expectedCount] or timeout. 341 - /// Returns the thread map if successful, or null if timeout. 342 - Future<GalleryThread?> pollGalleryThreadComments({ 343 - required String galleryUri, 344 - required int expectedCount, 345 - Duration pollDelay = const Duration(seconds: 2), 346 - int maxAttempts = 20, 347 - }) async { 348 - int attempts = 0; 349 - GalleryThread? thread; 350 - while (attempts < maxAttempts) { 351 - thread = await getGalleryThread(uri: galleryUri); 352 - if (thread != null && thread.comments.length == expectedCount) { 353 - appLogger.i('Gallery thread $galleryUri has expected number of comments: $expectedCount'); 354 - return thread; 355 - } 356 - await Future.delayed(pollDelay); 357 - attempts++; 358 } 359 - appLogger.w( 360 - 'Gallery thread $galleryUri did not reach expected comments count ($expectedCount) after polling.', 361 ); 362 - return null; 363 } 364 365 - /// Uploads a blob (file) to the atproto uploadBlob endpoint using DPoP authentication. 366 - /// Returns the blob reference map on success, or null on failure. 367 - Future<Map<String, dynamic>?> uploadBlob(File file) async { 368 final session = await auth.getValidSession(); 369 if (session == null) { 370 - appLogger.w('No valid session for uploadBlob'); 371 - return null; 372 } 373 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 374 - final issuer = session.issuer; 375 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.uploadBlob'); 376 377 // Detect MIME type, fallback to application/octet-stream if unknown 378 String? mimeType = lookupMimeType(file.path); 379 final contentType = mimeType ?? 'application/octet-stream'; 380 381 - appLogger.i('Uploading blob: ${file.path} (MIME: $mimeType)'); 382 - 383 final bytes = await file.readAsBytes(); 384 385 - final response = await dpopClient.send( 386 - method: 'POST', 387 - url: url, 388 - accessToken: session.accessToken, 389 - headers: {'Content-Type': contentType}, 390 body: bytes, 391 ); 392 393 if (response.statusCode != 200 && response.statusCode != 201) { 394 appLogger.w( 395 - 'Failed to upload blob: \\${response.statusCode} \\${response.body} (File: \\${file.path}, MIME: \\${mimeType})', 396 ); 397 - return null; 398 } 399 400 try { 401 - final result = jsonDecode(response.body) as Map<String, dynamic>; 402 - appLogger.i('Uploaded blob result: $result'); 403 - return result; 404 } catch (e, st) { 405 - appLogger.e('Failed to parse uploadBlob response: $e', stackTrace: st); 406 - return null; 407 } 408 } 409 410 - Future<String?> createPhoto({ 411 - required Map<String, dynamic> blob, 412 - required int width, 413 - required int height, 414 - String alt = '', 415 }) async { 416 final session = await auth.getValidSession(); 417 - if (session == null) { 418 - appLogger.w('No valid session for createPhotoRecord'); 419 - return null; 420 } 421 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 422 - final issuer = session.issuer; 423 - final did = session.subject; 424 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 425 - final record = { 426 - 'collection': 'social.grain.photo', 427 - 'repo': did, 428 - 'record': { 429 - 'photo': blob['blob'], 430 - 'aspectRatio': {'width': width, 'height': height}, 431 - 'alt': "", 432 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 433 - }, 434 - }; 435 - appLogger.i('Creating photo record: $record'); 436 - final response = await dpopClient.send( 437 - method: 'POST', 438 - url: url, 439 - accessToken: session.accessToken, 440 - headers: {'Content-Type': 'application/json'}, 441 - body: jsonEncode(record), 442 ); 443 - if (response.statusCode != 200 && response.statusCode != 201) { 444 - appLogger.w('Failed to create photo record: \\${response.statusCode} \\${response.body}'); 445 - return null; 446 } 447 - final result = jsonDecode(response.body) as Map<String, dynamic>; 448 - appLogger.i('Created photo record result: $result'); 449 - return result['uri'] as String?; 450 } 451 452 - Future<String?> createGalleryItem({ 453 - required String galleryUri, 454 - required String photoUri, 455 - required int position, 456 }) async { 457 final session = await auth.getValidSession(); 458 - if (session == null) { 459 - appLogger.w('No valid session for createGalleryItem'); 460 - return null; 461 } 462 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 463 - final issuer = session.issuer; 464 - final did = session.subject; 465 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 466 - final record = { 467 - 'collection': 'social.grain.gallery.item', 468 - 'repo': did, 469 - 'record': { 470 - 'gallery': galleryUri, 471 - 'item': photoUri, 472 - 'position': position, 473 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 474 - }, 475 - }; 476 - appLogger.i('Creating gallery item: $record'); 477 - final response = await dpopClient.send( 478 - method: 'POST', 479 - url: url, 480 - accessToken: session.accessToken, 481 - headers: {'Content-Type': 'application/json'}, 482 - body: jsonEncode(record), 483 ); 484 - if (response.statusCode != 200 && response.statusCode != 201) { 485 - appLogger.w('Failed to create gallery item: \\${response.statusCode} \\${response.body}'); 486 - return null; 487 } 488 - final result = jsonDecode(response.body) as Map<String, dynamic>; 489 - appLogger.i('Created gallery item result: $result'); 490 - return result['uri'] as String?; 491 } 492 493 - Future<String?> createComment({ 494 - required String text, 495 - List<Map<String, dynamic>>? facets, 496 - required String subject, 497 - String? focus, // Now a String (photo URI) 498 - String? replyTo, 499 - }) async { 500 final session = await auth.getValidSession(); 501 - if (session == null) { 502 - appLogger.w('No valid session for createComment'); 503 - return null; 504 } 505 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 506 - final issuer = session.issuer; 507 - final did = session.subject; 508 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 509 - final record = { 510 - 'collection': 'social.grain.comment', 511 - 'repo': did, 512 - 'record': { 513 - 'text': text, 514 - if (facets != null) 'facets': facets, 515 - 'subject': subject, 516 - if (focus != null) 'focus': focus, // focus is now a String 517 - if (replyTo != null) 'replyTo': replyTo, 518 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 519 - }, 520 - }; 521 - appLogger.i('Creating comment: $record'); 522 - final response = await dpopClient.send( 523 - method: 'POST', 524 - url: url, 525 - accessToken: session.accessToken, 526 - headers: {'Content-Type': 'application/json'}, 527 - body: jsonEncode(record), 528 ); 529 - if (response.statusCode != 200 && response.statusCode != 201) { 530 - appLogger.w('Failed to create comment: \\${response.statusCode} \\${response.body}'); 531 - return null; 532 } 533 - final result = jsonDecode(response.body) as Map<String, dynamic>; 534 - appLogger.i('Created comment result: $result'); 535 - return result['uri'] as String?; 536 } 537 538 - Future<String?> createFavorite({required String galleryUri}) async { 539 final session = await auth.getValidSession(); 540 - if (session == null) { 541 - appLogger.w('No valid session for createFavorite'); 542 - return null; 543 } 544 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 545 - final issuer = session.issuer; 546 - final did = session.subject; 547 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 548 - final record = { 549 - 'collection': 'social.grain.favorite', 550 - 'repo': did, 551 - 'record': {'subject': galleryUri, 'createdAt': DateTime.now().toUtc().toIso8601String()}, 552 - }; 553 - appLogger.i('Creating favorite: $record'); 554 - final response = await dpopClient.send( 555 - method: 'POST', 556 - url: url, 557 - accessToken: session.accessToken, 558 - headers: {'Content-Type': 'application/json'}, 559 - body: jsonEncode(record), 560 ); 561 - if (response.statusCode != 200 && response.statusCode != 201) { 562 - appLogger.w('Failed to create favorite: \\${response.statusCode} \\${response.body}'); 563 - return null; 564 } 565 - final result = jsonDecode(response.body) as Map<String, dynamic>; 566 - appLogger.i('Created favorite result: $result'); 567 - return result['uri'] as String?; 568 } 569 570 - Future<String?> createFollow({required String followeeDid}) async { 571 final session = await auth.getValidSession(); 572 - if (session == null) { 573 - appLogger.w('No valid session for createFollow'); 574 - return null; 575 } 576 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 577 - final issuer = session.issuer; 578 - final did = session.subject; 579 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 580 - final record = { 581 - 'collection': 'social.grain.graph.follow', 582 - 'repo': did, 583 - 'record': {'subject': followeeDid, 'createdAt': DateTime.now().toUtc().toIso8601String()}, 584 - }; 585 - appLogger.i('Creating follow: $record'); 586 - final response = await dpopClient.send( 587 - method: 'POST', 588 - url: url, 589 - accessToken: session.accessToken, 590 - headers: {'Content-Type': 'application/json'}, 591 - body: jsonEncode(record), 592 ); 593 - if (response.statusCode != 200 && response.statusCode != 201) { 594 - appLogger.w('Failed to create follow: \\${response.statusCode} \\${response.body}'); 595 - return null; 596 } 597 - final result = jsonDecode(response.body) as Map<String, dynamic>; 598 - appLogger.i('Created follow result: $result'); 599 - return result['uri'] as String?; 600 } 601 602 - /// Deletes a record by its URI using DPoP authentication. 603 - /// Returns true on success, false on failure. 604 - Future<bool> deleteRecord(String uri) async { 605 final session = await auth.getValidSession(); 606 - if (session == null) { 607 - appLogger.w('No valid session for deleteRecord'); 608 - return false; 609 - } 610 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 611 - final issuer = session.issuer; 612 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.deleteRecord'); 613 - final repo = session.subject; 614 - if (repo.isEmpty) { 615 - appLogger.w('No repo (DID) available from session for deleteRecord'); 616 - return false; 617 - } 618 - String? collection; 619 - String? rkey; 620 - try { 621 - final atUri = AtUri.parse(uri); 622 - collection = atUri.collection.toString(); 623 - rkey = atUri.rkey; 624 - } catch (e) { 625 - appLogger.w('Failed to parse collection from uri: $uri'); 626 } 627 - if (collection == null || collection.isEmpty) { 628 - appLogger.w('No collection found in uri: $uri'); 629 - return false; 630 - } 631 - final payload = {'uri': uri, 'repo': repo, 'collection': collection, 'rkey': rkey}; 632 - appLogger.i('Deleting record: $payload'); 633 - final response = await dpopClient.send( 634 - method: 'POST', 635 - url: url, 636 - accessToken: session.accessToken, 637 - headers: {'Content-Type': 'application/json'}, 638 - body: jsonEncode(payload), 639 ); 640 - if (response.statusCode != 200 && response.statusCode != 204) { 641 - appLogger.w('Failed to delete record: \\${response.statusCode} \\${response.body}'); 642 - return false; 643 } 644 - appLogger.i('Deleted record $uri'); 645 - return true; 646 } 647 648 - /// Updates the current user's profile (displayName, description, avatar). 649 - /// If avatarFile is provided, uploads it as a blob and sets avatar. 650 - /// Returns true on success, false on failure. 651 - Future<bool> updateProfile({ 652 - required String displayName, 653 - required String description, 654 - File? avatarFile, 655 - }) async { 656 final session = await auth.getValidSession(); 657 - if (session == null) { 658 - appLogger.w('No valid session for updateProfile'); 659 - return false; 660 - } 661 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 662 - final issuer = session.issuer; 663 - final did = session.subject; 664 - // Fetch the raw profile record from atproto getRecord endpoint 665 - final getUrl = Uri.parse( 666 - '$issuer/xrpc/com.atproto.repo.getRecord?repo=$did&collection=social.grain.actor.profile&rkey=self', 667 - ); 668 - final getResp = await dpopClient.send( 669 - method: 'GET', 670 - url: getUrl, 671 - accessToken: session.accessToken, 672 - headers: {'Content-Type': 'application/json'}, 673 - ); 674 - if (getResp.statusCode != 200) { 675 - appLogger.w( 676 - 'Failed to fetch raw profile record for update: \\${getResp.statusCode} \\${getResp.body}', 677 - ); 678 - return false; 679 - } 680 - final recordJson = jsonDecode(getResp.body) as Map<String, dynamic>; 681 - var avatar = recordJson['value']?['avatar']; 682 - // If avatarFile is provided, upload it and set avatar 683 - if (avatarFile != null) { 684 - try { 685 - // Resize avatar before upload using photo_manip 686 - final resizeResult = await resizeImage(file: avatarFile); 687 - final blobResult = await uploadBlob(resizeResult.file); 688 - if (blobResult != null && blobResult['blob'] != null) { 689 - avatar = blobResult['blob']; 690 - } 691 - } catch (e) { 692 - appLogger.w('Failed to upload avatar: $e'); 693 - } 694 - } 695 - // Update the profile record 696 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.putRecord'); 697 - final record = { 698 - 'collection': 'social.grain.actor.profile', 699 - 'repo': did, 700 - 'rkey': 'self', 701 - 'record': {'displayName': displayName, 'description': description, 'avatar': avatar}, 702 - }; 703 - appLogger.i('Updating profile: $record'); 704 - final response = await dpopClient.send( 705 - method: 'POST', 706 - url: url, 707 - accessToken: session.accessToken, 708 - headers: {'Content-Type': 'application/json'}, 709 - body: jsonEncode(record), 710 - ); 711 - if (response.statusCode != 200 && response.statusCode != 201) { 712 - appLogger.w('Failed to update profile: \\${response.statusCode} \\${response.body}'); 713 - return false; 714 } 715 - appLogger.i('Profile updated successfully'); 716 - return true; 717 - } 718 - 719 - /// Fetch followers for a given actor DID 720 - Future<FollowersResult> getFollowers({ 721 - required String actor, 722 - String? cursor, 723 - int limit = 50, 724 - }) async { 725 - final uri = Uri.parse( 726 - '$_apiUrl/xrpc/social.grain.graph.getFollowers?actor=$actor&limit=$limit${cursor != null ? '&cursor=$cursor' : ''}', 727 ); 728 - final response = await http.get(uri, headers: {'Content-Type': 'application/json'}); 729 if (response.statusCode != 200) { 730 - throw Exception('Failed to fetch followers: \\${response.statusCode} \\${response.body}'); 731 } 732 final json = jsonDecode(response.body); 733 - return FollowersResult.fromJson(json); 734 } 735 736 - /// Fetch follows for a given actor DID 737 - Future<FollowsResult> getFollows({required String actor, String? cursor, int limit = 50}) async { 738 - final uri = Uri.parse( 739 - '$_apiUrl/xrpc/social.grain.graph.getFollows?actor=$actor&limit=$limit${cursor != null ? '&cursor=$cursor' : ''}', 740 ); 741 - final response = await http.get(uri, headers: {'Content-Type': 'application/json'}); 742 if (response.statusCode != 200) { 743 - throw Exception('Failed to fetch follows: \\${response.statusCode} \\${response.body}'); 744 } 745 final json = jsonDecode(response.body); 746 - return FollowsResult.fromJson(json); 747 } 748 749 - /// Updates the sort order of gallery items using com.atproto.repo.applyWrites 750 - /// [galleryUri]: The URI of the gallery (at://did/social.grain.gallery/rkey) 751 - /// [sortedItemUris]: List of item URIs in the desired order 752 - /// [itemsMeta]: List of GalleryItem meta objects (must include gallery, item, createdAt, uri) 753 - /// Returns true on success, false on failure 754 - Future<bool> updateGallerySortOrder({ 755 - required String galleryUri, 756 - required List<GalleryItem> orderedItems, 757 - }) async { 758 final session = await auth.getValidSession(); 759 - if (session == null) { 760 - appLogger.w('No valid session for updateGallerySortOrder'); 761 - return false; 762 - } 763 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 764 - final issuer = session.issuer; 765 - final did = session.subject; 766 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.applyWrites'); 767 - 768 - final updates = <Map<String, dynamic>>[]; 769 - int position = 0; 770 - for (final item in orderedItems) { 771 - String rkey = ''; 772 - try { 773 - rkey = AtUri.parse(item.uri).rkey; 774 - } catch (_) {} 775 - updates.add({ 776 - '\$type': 'com.atproto.repo.applyWrites#update', 777 - 'collection': 'social.grain.gallery.item', 778 - 'rkey': rkey, 779 - 'value': { 780 - 'gallery': item.gallery, 781 - 'item': item.item, 782 - 'createdAt': item.createdAt, 783 - 'position': position, 784 - }, 785 - }); 786 - position++; 787 } 788 - if (updates.isEmpty) { 789 - appLogger.w('No updates to apply for gallery sort order'); 790 - return false; 791 - } 792 - final payload = {'repo': did, 'validate': false, 'writes': updates}; 793 - appLogger.i('Applying gallery sort order updates: $payload'); 794 - final response = await dpopClient.send( 795 - method: 'POST', 796 - url: url, 797 - accessToken: session.accessToken, 798 - headers: {'Content-Type': 'application/json'}, 799 - body: jsonEncode(payload), 800 ); 801 - if (response.statusCode != 200 && response.statusCode != 201) { 802 - appLogger.w( 803 - 'Failed to apply gallery sort order: \\${response.statusCode} \\${response.body}', 804 - ); 805 - return false; 806 } 807 - appLogger.i('Gallery sort order updated successfully'); 808 - return true; 809 } 810 811 - /// Updates a gallery's title and description. 812 - /// Returns true on success, false on failure. 813 - Future<bool> updateGallery({ 814 - required String galleryUri, 815 - required String title, 816 - required String description, 817 - required String createdAt, 818 - List<Map<String, dynamic>>? facets, 819 - }) async { 820 final session = await auth.getValidSession(); 821 - if (session == null) { 822 - appLogger.w('No valid session for updateGallery'); 823 return false; 824 } 825 - final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 826 - final issuer = session.issuer; 827 - final did = session.subject; 828 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.putRecord'); 829 - // Extract rkey from galleryUri 830 - String rkey = ''; 831 try { 832 - rkey = AtUri.parse(galleryUri).rkey; 833 - } catch (_) {} 834 - if (rkey.isEmpty) { 835 - appLogger.w('No rkey found in galleryUri: $galleryUri'); 836 return false; 837 } 838 - final record = { 839 - 'collection': 'social.grain.gallery', 840 - 'repo': did, 841 - 'rkey': rkey, 842 - 'record': { 843 - 'title': title, 844 - 'description': description, 845 - if (facets != null) 'facets': facets, 846 - 'updatedAt': DateTime.now().toUtc().toIso8601String(), 847 - 'createdAt': createdAt, 848 - }, 849 - }; 850 - appLogger.i('Updating gallery: $record'); 851 - final response = await dpopClient.send( 852 - method: 'POST', 853 - url: url, 854 - accessToken: session.accessToken, 855 - headers: {'Content-Type': 'application/json'}, 856 - body: jsonEncode(record), 857 - ); 858 - if (response.statusCode != 200 && response.statusCode != 201) { 859 - appLogger.w('Failed to update gallery: ${response.statusCode} ${response.body}'); 860 - return false; 861 - } 862 - appLogger.i('Gallery updated successfully'); 863 - return true; 864 } 865 } 866
··· 1 import 'dart:convert'; 2 import 'dart:io'; 3 4 import 'package:grain/app_logger.dart'; 5 import 'package:grain/main.dart'; 6 + import 'package:grain/models/session.dart'; 7 import 'package:http/http.dart' as http; 8 import 'package:mime/mime.dart'; 9 10 import './auth.dart'; 11 import 'models/followers_result.dart'; 12 import 'models/follows_result.dart'; 13 import 'models/gallery.dart'; 14 import 'models/gallery_photo.dart'; 15 import 'models/gallery_thread.dart'; 16 import 'models/notification.dart' as grain; 17 + import 'models/procedures/procedures.dart'; 18 import 'models/profile.dart'; 19 20 class ApiService { 21 Profile? currentUser; 22 Profile? loadedProfile; 23 List<Gallery> galleries = []; 24 25 String get _apiUrl => AppConfig.apiUrl; 26 27 + Future<Session?> refreshSession(Session session) async { 28 + final url = Uri.parse('$_apiUrl/api/token/refresh'); 29 + final headers = {'Content-Type': 'application/json'}; 30 + try { 31 + final response = await http.post( 32 + url, 33 + headers: headers, 34 + body: jsonEncode({'refreshToken': session.refreshToken}), 35 + ); 36 + if (response.statusCode == 200) { 37 + appLogger.i('Session refreshed successfully'); 38 + return Session.fromJson(jsonDecode(response.body)); 39 + } else { 40 + appLogger.w('Failed to refresh session: ${response.statusCode} ${response.body}'); 41 + return null; 42 + } 43 + } catch (e) { 44 + appLogger.e('Error refreshing session: $e'); 45 + return null; 46 } 47 } 48 49 + Future<bool> revokeSession(Session session) async { 50 + final url = Uri.parse('$_apiUrl/api/token/revoke'); 51 + final headers = {'Content-Type': 'application/json'}; 52 + try { 53 + final response = await http.post( 54 + url, 55 + headers: headers, 56 + body: jsonEncode({'refreshToken': session.refreshToken}), 57 + ); 58 + if (response.statusCode == 200) { 59 + appLogger.i('Session revoked successfully'); 60 + return true; 61 + } else { 62 + appLogger.w('Failed to revoke session: ${response.statusCode} ${response.body}'); 63 + return false; 64 + } 65 + } catch (e) { 66 + appLogger.e('Error revoking session: $e'); 67 + return false; 68 } 69 } 70 71 Future<Profile?> fetchCurrentUser() async { 72 final session = await auth.getValidSession(); 73 74 + if (session == null || session.did.isEmpty) { 75 return null; 76 } 77 78 + final user = await fetchProfile(did: session.did); 79 80 currentUser = user; 81 ··· 83 } 84 85 Future<Profile?> fetchProfile({required String did}) async { 86 + final session = await auth.getValidSession(); 87 + final token = session?.token; 88 appLogger.i('Fetching profile for did: $did'); 89 final response = await http.get( 90 Uri.parse('$_apiUrl/xrpc/social.grain.actor.getProfile?actor=$did'), 91 + headers: {'Content-Type': 'application/json', 'Authorization': "Bearer $token"}, 92 ); 93 if (response.statusCode != 200) { 94 appLogger.w('Failed to fetch profile: ${response.statusCode} ${response.body}'); ··· 129 } 130 131 Future<List<Gallery>> getTimeline({String? algorithm}) async { 132 + final session = await auth.getValidSession(); 133 + final token = session?.token; 134 + if (token == null) { 135 return []; 136 } 137 + appLogger.i('Fetching timeline with algorithm: ${algorithm ?? 'default'}'); 138 final uri = algorithm != null 139 ? Uri.parse('$_apiUrl/xrpc/social.grain.feed.getTimeline?algorithm=$algorithm') 140 : Uri.parse('$_apiUrl/xrpc/social.grain.feed.getTimeline'); 141 final response = await http.get( 142 uri, 143 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 144 ); 145 if (response.statusCode != 200) { 146 appLogger.w('Failed to fetch timeline: ${response.statusCode} ${response.body}'); ··· 155 156 Future<Gallery?> getGallery({required String uri}) async { 157 appLogger.i('Fetching gallery for uri: $uri'); 158 + final session = await auth.getValidSession(); 159 + final token = session?.token; 160 + if (token == null) { 161 + appLogger.w('No access token for getGallery'); 162 + return null; 163 + } 164 final response = await http.get( 165 Uri.parse('$_apiUrl/xrpc/social.grain.gallery.getGallery?uri=$uri'), 166 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 167 ); 168 if (response.statusCode != 200) { 169 appLogger.w('Failed to fetch gallery: ${response.statusCode} ${response.body}'); ··· 185 186 Future<GalleryThread?> getGalleryThread({required String uri}) async { 187 appLogger.i('Fetching gallery thread for uri: $uri'); 188 + final session = await auth.getValidSession(); 189 + final token = session?.token; 190 + if (token == null) { 191 + appLogger.w('No access token for getGalleryThread'); 192 + return null; 193 + } 194 final response = await http.get( 195 Uri.parse('$_apiUrl/xrpc/social.grain.gallery.getGalleryThread?uri=$uri'), 196 + headers: {'Content-Type': 'application/json', 'Authorization': "Bearer $token"}, 197 ); 198 if (response.statusCode != 200) { 199 + appLogger.w('Failed to fetch gallery thread: ${response.statusCode} ${response.body}'); 200 return null; 201 } 202 final json = jsonDecode(response.body) as Map<String, dynamic>; ··· 204 } 205 206 Future<List<grain.Notification>> getNotifications() async { 207 + final session = await auth.getValidSession(); 208 + final token = session?.token; 209 + if (token == null) { 210 appLogger.w('No access token for getNotifications'); 211 return []; 212 } 213 appLogger.i('Fetching notifications'); 214 final response = await http.get( 215 Uri.parse('$_apiUrl/xrpc/social.grain.notification.getNotifications'), 216 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 217 ); 218 if (response.statusCode != 200) { 219 + appLogger.w('Failed to fetch notifications: ${response.statusCode} ${response.body}'); 220 return []; 221 } 222 final json = jsonDecode(response.body); ··· 253 } 254 255 Future<List<Profile>> searchActors(String query) async { 256 + final session = await auth.getValidSession(); 257 + final token = session?.token; 258 + if (token == null) { 259 appLogger.w('No access token for searchActors'); 260 return []; 261 } 262 appLogger.i('Searching actors with query: $query'); 263 final response = await http.get( 264 Uri.parse('$_apiUrl/xrpc/social.grain.actor.searchActors?q=$query'), 265 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 266 ); 267 if (response.statusCode != 200) { 268 appLogger.w('Failed to search actors: ${response.statusCode} ${response.body}'); ··· 286 return (json['items'] as List<dynamic>?)?.map((item) => Gallery.fromJson(item)).toList() ?? []; 287 } 288 289 + /// Fetch followers for a given actor DID 290 + Future<FollowersResult> getFollowers({ 291 + required String actor, 292 + String? cursor, 293 + int limit = 50, 294 }) async { 295 + final uri = Uri.parse( 296 + '$_apiUrl/xrpc/social.grain.graph.getFollowers?actor=$actor&limit=$limit${cursor != null ? '&cursor=$cursor' : ''}', 297 + ); 298 + final response = await http.get(uri, headers: {'Content-Type': 'application/json'}); 299 + if (response.statusCode != 200) { 300 + throw Exception('Failed to fetch followers: \\${response.statusCode} \\${response.body}'); 301 + } 302 + final json = jsonDecode(response.body); 303 + return FollowersResult.fromJson(json); 304 + } 305 + 306 + /// Fetch follows for a given actor DID 307 + Future<FollowsResult> getFollows({required String actor, String? cursor, int limit = 50}) async { 308 + final uri = Uri.parse( 309 + '$_apiUrl/xrpc/social.grain.graph.getFollows?actor=$actor&limit=$limit${cursor != null ? '&cursor=$cursor' : ''}', 310 + ); 311 + final response = await http.get(uri, headers: {'Content-Type': 'application/json'}); 312 + if (response.statusCode != 200) { 313 + throw Exception('Failed to fetch follows: \\${response.statusCode} \\${response.body}'); 314 + } 315 + final json = jsonDecode(response.body); 316 + return FollowsResult.fromJson(json); 317 + } 318 + 319 + // Procedures 320 + 321 + Future<UpdateProfileResponse> updateProfile({required UpdateProfileRequest request}) async { 322 final session = await auth.getValidSession(); 323 + final token = session?.token; 324 + if (token == null) { 325 + throw Exception('No access token for updateProfile'); 326 } 327 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.actor.updateProfile'); 328 + final response = await http.post( 329 + uri, 330 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 331 + body: jsonEncode(request.toJson()), 332 ); 333 + if (response.statusCode != 200) { 334 + throw Exception('Failed to update profile: ${response.statusCode} ${response.body}'); 335 } 336 + final json = jsonDecode(response.body); 337 + return UpdateProfileResponse.fromJson(json); 338 } 339 340 + Future<UpdateAvatarResponse> updateAvatar({required File avatarFile}) async { 341 + final session = await auth.getValidSession(); 342 + final token = session?.token; 343 + if (token == null) { 344 + throw Exception('No access token for updateAvatar'); 345 } 346 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.actor.updateAvatar'); 347 + String? mimeType = lookupMimeType(avatarFile.path); 348 + final contentType = mimeType ?? 'application/octet-stream'; 349 + final bytes = await avatarFile.readAsBytes(); 350 + final response = await http.post( 351 + uri, 352 + headers: {'Authorization': "Bearer $token", 'Content-Type': contentType}, 353 + body: bytes, 354 ); 355 + if (response.statusCode != 200) { 356 + throw Exception('Failed to update avatar: ${response.statusCode} ${response.body}'); 357 + } 358 + final json = jsonDecode(response.body); 359 + return UpdateAvatarResponse.fromJson(json); 360 } 361 362 + Future<ApplySortResponse> applySort({required ApplySortRequest request}) async { 363 + final session = await auth.getValidSession(); 364 + final token = session?.token; 365 + if (token == null) { 366 + throw Exception('No access token for applySort'); 367 } 368 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.gallery.applySort'); 369 + final response = await http.post( 370 + uri, 371 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 372 + body: jsonEncode(request.toJson()), 373 ); 374 + if (response.statusCode != 200) { 375 + throw Exception('Failed to apply sort: ${response.statusCode} ${response.body}'); 376 + } 377 + final json = jsonDecode(response.body); 378 + return ApplySortResponse.fromJson(json); 379 } 380 381 + Future<ApplyAltsResponse> applyAlts({required ApplyAltsRequest request}) async { 382 + final session = await auth.getValidSession(); 383 + final token = session?.token; 384 + if (token == null) { 385 + throw Exception('No access token for applyAlts'); 386 + } 387 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.photo.applyAlts'); 388 + final response = await http.post( 389 + uri, 390 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 391 + body: jsonEncode(request.toJson()), 392 + ); 393 + if (response.statusCode != 200) { 394 + throw Exception('Failed to apply alts: ${response.statusCode} ${response.body}'); 395 + } 396 + final json = jsonDecode(response.body); 397 + return ApplyAltsResponse.fromJson(json); 398 + } 399 + 400 + Future<CreateExifResponse> createExif({required CreateExifRequest request}) async { 401 + final session = await auth.getValidSession(); 402 + final token = session?.token; 403 + if (token == null) { 404 + throw Exception('No access token for createExif'); 405 + } 406 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.photo.createExif'); 407 + final response = await http.post( 408 + uri, 409 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 410 + body: jsonEncode(request.toJson()), 411 + ); 412 + if (response.statusCode != 200) { 413 + throw Exception('Failed to create exif: ${response.statusCode} ${response.body}'); 414 + } 415 + final json = jsonDecode(response.body); 416 + return CreateExifResponse.fromJson(json); 417 + } 418 + 419 + Future<CreateFollowResponse> createFollow({required CreateFollowRequest request}) async { 420 + final session = await auth.getValidSession(); 421 + final token = session?.token; 422 + if (token == null) { 423 + throw Exception('No access token for createFollow'); 424 + } 425 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.graph.createFollow'); 426 + final response = await http.post( 427 + uri, 428 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 429 + body: jsonEncode(request.toJson()), 430 + ); 431 + if (response.statusCode != 200) { 432 + throw Exception('Failed to create follow: ${response.statusCode} ${response.body}'); 433 + } 434 + final json = jsonDecode(response.body); 435 + return CreateFollowResponse.fromJson(json); 436 + } 437 + 438 + Future<DeleteFollowResponse> deleteFollow({required DeleteFollowRequest request}) async { 439 + final session = await auth.getValidSession(); 440 + final token = session?.token; 441 + if (token == null) { 442 + throw Exception('No access token for deleteFollow'); 443 + } 444 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.graph.deleteFollow'); 445 + final response = await http.post( 446 + uri, 447 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 448 + body: jsonEncode(request.toJson()), 449 + ); 450 + if (response.statusCode != 200) { 451 + throw Exception('Failed to delete follow: {response.statusCode} {response.body}'); 452 + } 453 + final json = jsonDecode(response.body); 454 + return DeleteFollowResponse.fromJson(json); 455 + } 456 + 457 + Future<DeletePhotoResponse> deletePhoto({required DeletePhotoRequest request}) async { 458 + final session = await auth.getValidSession(); 459 + final token = session?.token; 460 + if (token == null) { 461 + throw Exception('No access token for deletePhoto'); 462 + } 463 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.photo.deletePhoto'); 464 + final response = await http.post( 465 + uri, 466 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 467 + body: jsonEncode(request.toJson()), 468 + ); 469 + if (response.statusCode != 200) { 470 + throw Exception('Failed to delete photo: ${response.statusCode} ${response.body}'); 471 + } 472 + final json = jsonDecode(response.body); 473 + return DeletePhotoResponse.fromJson(json); 474 + } 475 + 476 + Future<UploadPhotoResponse> uploadPhoto(File file) async { 477 final session = await auth.getValidSession(); 478 if (session == null) { 479 + appLogger.w('No valid session for uploadPhoto'); 480 + throw Exception('No valid session for uploadPhoto'); 481 } 482 + final token = session.token; 483 + final uri = Uri.parse('${AppConfig.apiUrl}/xrpc/social.grain.photo.uploadPhoto'); 484 485 // Detect MIME type, fallback to application/octet-stream if unknown 486 String? mimeType = lookupMimeType(file.path); 487 final contentType = mimeType ?? 'application/octet-stream'; 488 489 + appLogger.i('Uploading photo: ${file.path} (MIME: $mimeType)'); 490 final bytes = await file.readAsBytes(); 491 492 + final response = await http.post( 493 + uri, 494 + headers: {'Authorization': 'Bearer $token', 'Content-Type': contentType}, 495 body: bytes, 496 ); 497 498 if (response.statusCode != 200 && response.statusCode != 201) { 499 appLogger.w( 500 + 'Failed to upload photo: ${response.statusCode} ${response.body} (File: ${file.path}, MIME: $mimeType)', 501 ); 502 + throw Exception('Failed to upload photo: ${response.statusCode} ${response.body}'); 503 } 504 505 try { 506 + final json = jsonDecode(response.body); 507 + appLogger.i('Uploaded photo result: $json'); 508 + return UploadPhotoResponse.fromJson(json); 509 } catch (e, st) { 510 + appLogger.e('Failed to parse createPhoto response: $e', stackTrace: st); 511 + throw Exception('Failed to parse createPhoto response: $e'); 512 } 513 } 514 515 + Future<DeleteGalleryItemResponse> deleteGalleryItem({ 516 + required DeleteGalleryItemRequest request, 517 }) async { 518 final session = await auth.getValidSession(); 519 + final token = session?.token; 520 + if (token == null) { 521 + throw Exception('No access token for deleteGalleryItem'); 522 } 523 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.gallery.deleteItem'); 524 + final response = await http.post( 525 + uri, 526 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 527 + body: jsonEncode(request.toJson()), 528 ); 529 + if (response.statusCode != 200) { 530 + throw Exception('Failed to delete gallery item: ${response.statusCode} ${response.body}'); 531 } 532 + final json = jsonDecode(response.body); 533 + return DeleteGalleryItemResponse.fromJson(json); 534 } 535 536 + Future<CreateGalleryItemResponse> createGalleryItem({ 537 + required CreateGalleryItemRequest request, 538 }) async { 539 final session = await auth.getValidSession(); 540 + final token = session?.token; 541 + if (token == null) { 542 + throw Exception('No access token for createGalleryItem'); 543 } 544 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.gallery.createItem'); 545 + final response = await http.post( 546 + uri, 547 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 548 + body: jsonEncode(request.toJson()), 549 ); 550 + if (response.statusCode != 200) { 551 + throw Exception('Failed to create gallery item: ${response.statusCode} ${response.body}'); 552 } 553 + final json = jsonDecode(response.body); 554 + return CreateGalleryItemResponse.fromJson(json); 555 } 556 557 + Future<UpdateGalleryResponse> updateGallery({required UpdateGalleryRequest request}) async { 558 final session = await auth.getValidSession(); 559 + final token = session?.token; 560 + if (token == null) { 561 + throw Exception('No access token for updateGallery'); 562 } 563 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.gallery.updateGallery'); 564 + final response = await http.post( 565 + uri, 566 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 567 + body: jsonEncode(request.toJson()), 568 ); 569 + if (response.statusCode != 200) { 570 + throw Exception('Failed to update gallery: ${response.statusCode} ${response.body}'); 571 } 572 + final json = jsonDecode(response.body); 573 + return UpdateGalleryResponse.fromJson(json); 574 } 575 576 + Future<DeleteGalleryResponse> deleteGallery({required DeleteGalleryRequest request}) async { 577 final session = await auth.getValidSession(); 578 + final token = session?.token; 579 + if (token == null) { 580 + throw Exception('No access token for deleteGallery'); 581 } 582 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.gallery.deleteGallery'); 583 + final response = await http.post( 584 + uri, 585 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 586 + body: jsonEncode(request.toJson()), 587 ); 588 + if (response.statusCode != 200) { 589 + throw Exception('Failed to delete gallery: ${response.statusCode} ${response.body}'); 590 } 591 + final json = jsonDecode(response.body); 592 + return DeleteGalleryResponse.fromJson(json); 593 } 594 595 + Future<CreateGalleryResponse> createGallery({required CreateGalleryRequest request}) async { 596 final session = await auth.getValidSession(); 597 + final token = session?.token; 598 + if (token == null) { 599 + throw Exception('No access token for createGallery'); 600 } 601 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.gallery.createGallery'); 602 + final response = await http.post( 603 + uri, 604 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 605 + body: jsonEncode(request.toJson()), 606 ); 607 + if (response.statusCode != 200) { 608 + throw Exception('Failed to create gallery: ${response.statusCode} ${response.body}'); 609 } 610 + final json = jsonDecode(response.body); 611 + return CreateGalleryResponse.fromJson(json); 612 } 613 614 + Future<DeleteFavoriteResponse> deleteFavorite({required DeleteFavoriteRequest request}) async { 615 final session = await auth.getValidSession(); 616 + final token = session?.token; 617 + if (token == null) { 618 + throw Exception('No access token for deleteFavorite'); 619 } 620 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.favorite.deleteFavorite'); 621 + final response = await http.post( 622 + uri, 623 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 624 + body: jsonEncode(request.toJson()), 625 ); 626 + if (response.statusCode != 200) { 627 + throw Exception('Failed to delete favorite: ${response.statusCode} ${response.body}'); 628 } 629 + final json = jsonDecode(response.body); 630 + return DeleteFavoriteResponse.fromJson(json); 631 } 632 633 + Future<CreateFavoriteResponse> createFavorite({required CreateFavoriteRequest request}) async { 634 final session = await auth.getValidSession(); 635 + final token = session?.token; 636 + if (token == null) { 637 + throw Exception('No access token for createFavorite'); 638 } 639 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.favorite.createFavorite'); 640 + final response = await http.post( 641 + uri, 642 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 643 + body: jsonEncode(request.toJson()), 644 ); 645 if (response.statusCode != 200) { 646 + throw Exception('Failed to create favorite: ${response.statusCode} ${response.body}'); 647 } 648 final json = jsonDecode(response.body); 649 + return CreateFavoriteResponse.fromJson(json); 650 } 651 652 + Future<DeleteCommentResponse> deleteComment({required DeleteCommentRequest request}) async { 653 + final session = await auth.getValidSession(); 654 + final token = session?.token; 655 + if (token == null) { 656 + throw Exception('No access token for deleteComment'); 657 + } 658 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.comment.deleteComment'); 659 + final response = await http.post( 660 + uri, 661 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 662 + body: jsonEncode(request.toJson()), 663 ); 664 if (response.statusCode != 200) { 665 + throw Exception('Failed to delete comment: ${response.statusCode} ${response.body}'); 666 } 667 final json = jsonDecode(response.body); 668 + return DeleteCommentResponse.fromJson(json); 669 } 670 671 + Future<CreateCommentResponse> createComment({required CreateCommentRequest request}) async { 672 final session = await auth.getValidSession(); 673 + final token = session?.token; 674 + if (token == null) { 675 + throw Exception('No access token for createComment'); 676 } 677 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.comment.createComment'); 678 + final response = await http.post( 679 + uri, 680 + headers: {'Authorization': "Bearer $token", 'Content-Type': 'application/json'}, 681 + body: jsonEncode(request.toJson()), 682 ); 683 + if (response.statusCode != 200) { 684 + throw Exception('Failed to create comment: ${response.statusCode} ${response.body}'); 685 } 686 + final json = jsonDecode(response.body); 687 + return CreateCommentResponse.fromJson(json); 688 } 689 690 + Future<bool> updateSeen() async { 691 final session = await auth.getValidSession(); 692 + final token = session?.token; 693 + if (token == null) { 694 + appLogger.w('No access token for updateSeen'); 695 return false; 696 } 697 + final url = Uri.parse('$_apiUrl/xrpc/social.grain.notification.updateSeen'); 698 + final seenAt = DateTime.now().toUtc().toIso8601String(); 699 + final body = jsonEncode({'seenAt': seenAt}); 700 + final headers = {'Authorization': 'Bearer $token', 'Content-Type': 'application/json'}; 701 try { 702 + final response = await http.post(url, headers: headers, body: body); 703 + if (response.statusCode == 200) { 704 + appLogger.i('Successfully updated seen notifications at $seenAt'); 705 + return true; 706 + } else { 707 + appLogger.w('Failed to update seen notifications: ${response.statusCode} ${response.body}'); 708 + return false; 709 + } 710 + } catch (e) { 711 + appLogger.e('Error updating seen notifications: $e'); 712 return false; 713 } 714 } 715 } 716
+5 -1
lib/app_icons.dart
··· 3 4 class AppIcons { 5 // Material Icons 6 static const IconData person = Icons.person; 7 static const IconData accountCircle = Icons.account_circle; 8 static const IconData close = Icons.close; ··· 15 static const IconData notifications = Icons.notifications; 16 static const IconData search = Icons.search; 17 static const IconData settings = Icons.settings; 18 // FontAwesome Icons 19 static const IconData house = FontAwesomeIcons.house; 20 static const IconData magnifyingGlass = FontAwesomeIcons.magnifyingGlass; ··· 29 static const IconData arrowUpFromBracket = FontAwesomeIcons.arrowUpFromBracket; 30 static const IconData bars = FontAwesomeIcons.bars; 31 static const IconData arrowRightFromBracket = FontAwesomeIcons.arrowRightFromBracket; 32 - static const IconData checkCircle = FontAwesomeIcons.checkCircle; 33 }
··· 3 4 class AppIcons { 5 // Material Icons 6 + static const IconData edit = Icons.edit; 7 + static const IconData sort = Icons.sort; 8 static const IconData person = Icons.person; 9 static const IconData accountCircle = Icons.account_circle; 10 static const IconData close = Icons.close; ··· 17 static const IconData notifications = Icons.notifications; 18 static const IconData search = Icons.search; 19 static const IconData settings = Icons.settings; 20 + static const IconData photoLibrary = Icons.photo_library; 21 + static const IconData moreVertical = Icons.more_vert; 22 // FontAwesome Icons 23 static const IconData house = FontAwesomeIcons.house; 24 static const IconData magnifyingGlass = FontAwesomeIcons.magnifyingGlass; ··· 33 static const IconData arrowUpFromBracket = FontAwesomeIcons.arrowUpFromBracket; 34 static const IconData bars = FontAwesomeIcons.bars; 35 static const IconData arrowRightFromBracket = FontAwesomeIcons.arrowRightFromBracket; 36 + static const IconData checkCircle = FontAwesomeIcons.circleCheck; 37 }
+91 -35
lib/auth.dart
··· 5 import 'package:grain/api.dart'; 6 import 'package:grain/app_logger.dart'; 7 import 'package:grain/main.dart'; 8 - import 'package:grain/models/atproto_session.dart'; 9 10 class Auth { 11 static const _storage = FlutterSecureStorage(); 12 Auth(); 13 14 Future<void> login(String handle) async { 15 final apiUrl = AppConfig.apiUrl; 16 - final redirectedUrl = await FlutterWebAuth2.authenticate( 17 - url: '$apiUrl/oauth/login?client=native&handle=${Uri.encodeComponent(handle)}', 18 - callbackUrlScheme: 'grainflutter', 19 - ); 20 - final uri = Uri.parse(redirectedUrl); 21 - final token = uri.queryParameters['token']; 22 23 - appLogger.i('Redirected URL: $redirectedUrl'); 24 appLogger.i('User signed in with handle: $handle'); 25 26 - apiService.setToken(token); 27 28 - final session = await apiService.fetchSession(); 29 - if (session == null) { 30 - throw Exception('Failed to fetch session after login'); 31 } 32 33 await _saveSession(session); 34 } 35 36 - Future<void> _saveSession(AtprotoSession session) async { 37 final sessionJson = jsonEncode(session.toJson()); 38 - await _storage.write(key: 'atproto_session', value: sessionJson); 39 } 40 41 - Future<AtprotoSession?> _loadSession() async { 42 - final jsonString = await _storage.read(key: 'atproto_session'); 43 - if (jsonString == null) return null; 44 45 try { 46 - final json = jsonDecode(jsonString); 47 - return AtprotoSession.fromJson(json); 48 } catch (e) { 49 // Optionally log or clear storage if corrupted 50 return null; 51 } 52 } 53 54 - bool isSessionExpired( 55 - AtprotoSession session, { 56 - Duration tolerance = const Duration(seconds: 30), 57 - }) { 58 final now = DateTime.now().toUtc(); 59 return session.expiresAt.subtract(tolerance).isBefore(now); 60 } 61 62 - Future<AtprotoSession?> getValidSession() async { 63 - var session = await _loadSession(); 64 - if (session == null || isSessionExpired(session)) { 65 - appLogger.w('Session is expired or not found, attempting refresh'); 66 - // Try to refresh session by calling fetchSession 67 try { 68 - final refreshed = await apiService.fetchSession(); 69 if (refreshed != null && !isSessionExpired(refreshed)) { 70 await _saveSession(refreshed); 71 appLogger.i('Session refreshed and saved'); 72 return refreshed; 73 } else { 74 - appLogger.w('Session refresh failed or still expired'); 75 return null; 76 } 77 } catch (e) { 78 appLogger.e('Error refreshing session: $e'); 79 return null; 80 } 81 } ··· 84 85 Future<void> clearSession() async { 86 // Remove session from secure storage 87 - await _storage.delete(key: 'atproto_session'); 88 - // Remove access token from secure storage and memory 89 - await apiService.setToken(null); 90 - // Optionally clear any in-memory session/user data 91 apiService.currentUser = null; 92 - // If you add a session property to ApiService, clear it here as well 93 } 94 } 95
··· 5 import 'package:grain/api.dart'; 6 import 'package:grain/app_logger.dart'; 7 import 'package:grain/main.dart'; 8 + import 'package:grain/models/session.dart'; 9 10 class Auth { 11 static const _storage = FlutterSecureStorage(); 12 Auth(); 13 14 + Future<bool> hasToken() async { 15 + final session = await _loadSession(); 16 + return session != null && session.token.isNotEmpty && !isSessionExpired(session); 17 + } 18 + 19 Future<void> login(String handle) async { 20 final apiUrl = AppConfig.apiUrl; 21 + String? token; 22 + String? refreshToken; 23 + String? expiresAtStr; 24 + String? did; 25 + 26 + try { 27 + final redirectUrl = await FlutterWebAuth2.authenticate( 28 + url: '$apiUrl/oauth/login?client=native&handle=${Uri.encodeComponent(handle)}', 29 + callbackUrlScheme: 'grainflutter', 30 + ); 31 + 32 + appLogger.i('Redirected URL: $redirectUrl'); 33 + 34 + final uri = Uri.parse(redirectUrl); 35 + token = uri.queryParameters['token']; 36 + refreshToken = uri.queryParameters['refreshToken']; 37 + expiresAtStr = uri.queryParameters['expiresAt']; 38 + did = uri.queryParameters['did']; 39 + } catch (e) { 40 + appLogger.e('Error during authentication: $e'); 41 + throw Exception('Authentication failed'); 42 + } 43 44 appLogger.i('User signed in with handle: $handle'); 45 46 + if (token == null || token.isEmpty) { 47 + throw Exception('No token found in redirect URL'); 48 + } 49 + if (refreshToken == null || refreshToken.isEmpty) { 50 + throw Exception('No refreshToken found in redirect URL'); 51 + } 52 + if (expiresAtStr == null || expiresAtStr.isEmpty) { 53 + throw Exception('No expiresAt found in redirect URL'); 54 + } 55 + if (did == null || did.isEmpty) { 56 + throw Exception('No did found in redirect URL'); 57 + } 58 59 + DateTime expiresAt; 60 + try { 61 + expiresAt = DateTime.parse(expiresAtStr); 62 + } catch (e) { 63 + throw Exception('Invalid expiresAt format'); 64 } 65 66 + final session = Session( 67 + token: token, 68 + refreshToken: refreshToken, 69 + expiresAt: expiresAt, 70 + did: did, 71 + ); 72 await _saveSession(session); 73 } 74 75 + Future<void> _saveSession(Session session) async { 76 final sessionJson = jsonEncode(session.toJson()); 77 + await _storage.write(key: 'session', value: sessionJson); 78 } 79 80 + Future<Session?> _loadSession() async { 81 + final sessionJsonString = await _storage.read(key: 'session'); 82 + if (sessionJsonString == null) return null; 83 84 try { 85 + final sessionJson = jsonDecode(sessionJsonString); 86 + return Session.fromJson(sessionJson); 87 } catch (e) { 88 // Optionally log or clear storage if corrupted 89 return null; 90 } 91 } 92 93 + bool isSessionExpired(Session session, {Duration tolerance = const Duration(seconds: 30)}) { 94 final now = DateTime.now().toUtc(); 95 return session.expiresAt.subtract(tolerance).isBefore(now); 96 } 97 98 + Future<Session?> getValidSession() async { 99 + final session = await _loadSession(); 100 + if (session == null) { 101 + // No session at all, do not attempt refresh 102 + return null; 103 + } 104 + if (isSessionExpired(session)) { 105 + appLogger.w('Session is expired, attempting refresh'); 106 try { 107 + final refreshed = await apiService.refreshSession(session); 108 if (refreshed != null && !isSessionExpired(refreshed)) { 109 await _saveSession(refreshed); 110 appLogger.i('Session refreshed and saved'); 111 return refreshed; 112 } else { 113 + appLogger.w('Session refresh failed or still expired, clearing session'); 114 + await clearSession(); 115 return null; 116 } 117 } catch (e) { 118 appLogger.e('Error refreshing session: $e'); 119 + await clearSession(); 120 return null; 121 } 122 } ··· 125 126 Future<void> clearSession() async { 127 // Remove session from secure storage 128 + await _storage.delete(key: 'session'); 129 + } 130 + 131 + Future<void> logout() async { 132 + final session = await _loadSession(); 133 + 134 + appLogger.i('Logging out user with session: $session'); 135 + 136 + // Clear any in-memory session/user data 137 apiService.currentUser = null; 138 + 139 + if (session == null) { 140 + appLogger.w('No session to revoke'); 141 + return; 142 + } 143 + 144 + await apiService.revokeSession(session); 145 + 146 + await clearSession(); 147 + 148 + appLogger.i('User logged out and session cleared'); 149 } 150 } 151
+69 -32
lib/main.dart
··· 6 import 'package:grain/app_logger.dart'; 7 import 'package:grain/app_theme.dart'; 8 import 'package:grain/auth.dart'; 9 import 'package:grain/screens/home_page.dart'; 10 import 'package:grain/screens/login_page.dart'; 11 12 - import 'providers/profile_provider.dart'; 13 import 'widgets/skeleton_timeline.dart'; 14 15 class AppConfig { 16 static late final String apiUrl; 17 18 static Future<void> init() async { 19 if (!kReleaseMode) { ··· 21 } 22 apiUrl = kReleaseMode 23 ? const String.fromEnvironment('API_URL', defaultValue: 'https://grain.social') 24 - : dotenv.env['API_URL'] ?? 'http://localhost:8080'; 25 } 26 } 27 ··· 32 appLogger.e('Flutter error: ${details.exception}\n${details.stack}'); 33 }; 34 await AppConfig.init(); 35 - await apiService.loadToken(); // Restore access token before app starts 36 appLogger.i('๐Ÿš€ App started'); 37 runApp(const ProviderScope(child: MyApp())); 38 } ··· 44 State<MyApp> createState() => _MyAppState(); 45 } 46 47 - class _MyAppState extends State<MyApp> { 48 bool isSignedIn = false; 49 bool _loading = true; 50 - String? displayName; 51 52 @override 53 void initState() { 54 super.initState(); 55 _checkToken(); 56 } 57 58 Future<void> _checkToken() async { 59 - await apiService.loadToken(); 60 - bool valid = false; 61 - if (apiService.hasToken) { 62 - try { 63 - final session = await apiService.fetchSession(); 64 - if (session != null) { 65 - await apiService.fetchCurrentUser(); 66 - valid = true; 67 - } else { 68 - // Session fetch failed, clear session 69 - await auth.clearSession(); 70 - } 71 - } catch (e) { 72 - // Error fetching session, clear session 73 - await auth.clearSession(); 74 - } 75 - } 76 setState(() { 77 isSignedIn = valid; 78 _loading = false; 79 }); 80 } 81 82 - void handleSignIn() async { 83 setState(() { 84 isSignedIn = true; 85 }); 86 - // Fetch current user profile from /oauth/session after login 87 appLogger.i('Fetching current user after sign in'); 88 await apiService.fetchCurrentUser(); 89 } 90 91 - void handleSignOut(BuildContext context) async { 92 - final container = ProviderScope.containerOf(context, listen: false); 93 - await auth.clearSession(); // Clear session data 94 - // Invalidate Riverpod providers for profile state 95 - container.invalidate(profileNotifierProvider); 96 - // Add any other providers you want to invalidate here 97 setState(() { 98 isSignedIn = false; 99 }); 100 } 101 102 @override ··· 115 ); 116 } else { 117 home = isSignedIn 118 - ? MyHomePage(title: 'Grain', onSignOut: () => handleSignOut(context)) 119 - : LoginPage(onSignIn: handleSignIn); 120 } 121 return MaterialApp( 122 title: 'Grain',
··· 6 import 'package:grain/app_logger.dart'; 7 import 'package:grain/app_theme.dart'; 8 import 'package:grain/auth.dart'; 9 + import 'package:grain/providers/notifications_provider.dart'; 10 + import 'package:grain/providers/profile_provider.dart'; 11 import 'package:grain/screens/home_page.dart'; 12 import 'package:grain/screens/login_page.dart'; 13 + import 'package:grain/websocket_service.dart'; 14 15 import 'widgets/skeleton_timeline.dart'; 16 17 class AppConfig { 18 static late final String apiUrl; 19 + static late final String wsUrl; 20 21 static Future<void> init() async { 22 if (!kReleaseMode) { ··· 24 } 25 apiUrl = kReleaseMode 26 ? const String.fromEnvironment('API_URL', defaultValue: 'https://grain.social') 27 + : dotenv.env['API_URL'] ?? ''; 28 + wsUrl = kReleaseMode 29 + ? const String.fromEnvironment( 30 + 'WS_URL', 31 + defaultValue: 'wss://notifications.grainsocial.network/ws', 32 + ) 33 + : dotenv.env['WS_URL'] ?? ''; 34 } 35 } 36 ··· 41 appLogger.e('Flutter error: ${details.exception}\n${details.stack}'); 42 }; 43 await AppConfig.init(); 44 appLogger.i('๐Ÿš€ App started'); 45 runApp(const ProviderScope(child: MyApp())); 46 } ··· 52 State<MyApp> createState() => _MyAppState(); 53 } 54 55 + class _MyAppState extends State<MyApp> with WidgetsBindingObserver { 56 bool isSignedIn = false; 57 bool _loading = true; 58 + WebSocketService? _wsService; 59 60 @override 61 void initState() { 62 super.initState(); 63 + WidgetsBinding.instance.addObserver(this); 64 _checkToken(); 65 } 66 67 + @override 68 + void dispose() { 69 + WidgetsBinding.instance.removeObserver(this); 70 + _disconnectWebSocket(); 71 + super.dispose(); 72 + } 73 + 74 + Future<void> _connectWebSocket() async { 75 + if (_wsService != null) return; // Already connected 76 + _disconnectWebSocket(); 77 + if (!isSignedIn) return; 78 + final session = await auth.getValidSession(); 79 + if (session == null) return; 80 + _wsService = WebSocketService( 81 + wsUrl: AppConfig.wsUrl, 82 + accessToken: session.token, 83 + onMessage: (message) { 84 + // Optionally: handle global messages or trigger provider updates 85 + }, 86 + ); 87 + _wsService!.connect(); 88 + } 89 + 90 + void _disconnectWebSocket() { 91 + _wsService?.disconnect(); 92 + _wsService = null; 93 + } 94 + 95 + @override 96 + void didChangeAppLifecycleState(AppLifecycleState state) { 97 + if (state == AppLifecycleState.resumed && isSignedIn) { 98 + // ignore: unawaited_futures 99 + _connectWebSocket(); 100 + } else if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { 101 + _disconnectWebSocket(); 102 + } 103 + } 104 + 105 Future<void> _checkToken() async { 106 + final user = await apiService.fetchCurrentUser(); 107 + final valid = user != null; 108 setState(() { 109 isSignedIn = valid; 110 _loading = false; 111 }); 112 } 113 114 + // Invalidate providers to refresh data 115 + void _invalidateProviders() { 116 + final container = ProviderScope.containerOf(context, listen: false); 117 + container.invalidate(profileNotifierProvider); 118 + container.invalidate(notificationsProvider); 119 + } 120 + 121 + void _handleSignIn() async { 122 setState(() { 123 isSignedIn = true; 124 }); 125 appLogger.i('Fetching current user after sign in'); 126 await apiService.fetchCurrentUser(); 127 + await _connectWebSocket(); 128 + _invalidateProviders(); 129 } 130 131 + void _handleSignOut(BuildContext context) async { 132 + await auth.logout(); 133 setState(() { 134 isSignedIn = false; 135 }); 136 + _disconnectWebSocket(); 137 } 138 139 @override ··· 152 ); 153 } else { 154 home = isSignedIn 155 + ? MyHomePage(title: 'Grain', onSignOut: () => _handleSignOut(context)) 156 + : LoginPage(onSignIn: _handleSignIn); 157 } 158 return MaterialApp( 159 title: 'Grain',
-19
lib/models/atproto_session.dart
··· 1 - import 'package:freezed_annotation/freezed_annotation.dart'; 2 - import 'package:jose/jose.dart'; 3 - 4 - part 'atproto_session.freezed.dart'; 5 - part 'atproto_session.g.dart'; 6 - 7 - @freezed 8 - class AtprotoSession with _$AtprotoSession { 9 - const factory AtprotoSession({ 10 - required String accessToken, 11 - required String tokenType, 12 - required DateTime expiresAt, 13 - required JsonWebKey dpopJwk, 14 - required String issuer, 15 - required String subject, 16 - }) = _AtprotoSession; 17 - 18 - factory AtprotoSession.fromJson(Map<String, dynamic> json) => _$AtprotoSessionFromJson(json); 19 - }
···
-293
lib/models/atproto_session.freezed.dart
··· 1 - // coverage:ignore-file 2 - // GENERATED CODE - DO NOT MODIFY BY HAND 3 - // ignore_for_file: type=lint 4 - // 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 5 - 6 - part of 'atproto_session.dart'; 7 - 8 - // ************************************************************************** 9 - // FreezedGenerator 10 - // ************************************************************************** 11 - 12 - T _$identity<T>(T value) => value; 13 - 14 - final _privateConstructorUsedError = UnsupportedError( 15 - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 - ); 17 - 18 - AtprotoSession _$AtprotoSessionFromJson(Map<String, dynamic> json) { 19 - return _AtprotoSession.fromJson(json); 20 - } 21 - 22 - /// @nodoc 23 - mixin _$AtprotoSession { 24 - String get accessToken => throw _privateConstructorUsedError; 25 - String get tokenType => throw _privateConstructorUsedError; 26 - DateTime get expiresAt => throw _privateConstructorUsedError; 27 - JsonWebKey get dpopJwk => throw _privateConstructorUsedError; 28 - String get issuer => throw _privateConstructorUsedError; 29 - String get subject => throw _privateConstructorUsedError; 30 - 31 - /// Serializes this AtprotoSession to a JSON map. 32 - Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 33 - 34 - /// Create a copy of AtprotoSession 35 - /// with the given fields replaced by the non-null parameter values. 36 - @JsonKey(includeFromJson: false, includeToJson: false) 37 - $AtprotoSessionCopyWith<AtprotoSession> get copyWith => 38 - throw _privateConstructorUsedError; 39 - } 40 - 41 - /// @nodoc 42 - abstract class $AtprotoSessionCopyWith<$Res> { 43 - factory $AtprotoSessionCopyWith( 44 - AtprotoSession value, 45 - $Res Function(AtprotoSession) then, 46 - ) = _$AtprotoSessionCopyWithImpl<$Res, AtprotoSession>; 47 - @useResult 48 - $Res call({ 49 - String accessToken, 50 - String tokenType, 51 - DateTime expiresAt, 52 - JsonWebKey dpopJwk, 53 - String issuer, 54 - String subject, 55 - }); 56 - } 57 - 58 - /// @nodoc 59 - class _$AtprotoSessionCopyWithImpl<$Res, $Val extends AtprotoSession> 60 - implements $AtprotoSessionCopyWith<$Res> { 61 - _$AtprotoSessionCopyWithImpl(this._value, this._then); 62 - 63 - // ignore: unused_field 64 - final $Val _value; 65 - // ignore: unused_field 66 - final $Res Function($Val) _then; 67 - 68 - /// Create a copy of AtprotoSession 69 - /// with the given fields replaced by the non-null parameter values. 70 - @pragma('vm:prefer-inline') 71 - @override 72 - $Res call({ 73 - Object? accessToken = null, 74 - Object? tokenType = null, 75 - Object? expiresAt = null, 76 - Object? dpopJwk = null, 77 - Object? issuer = null, 78 - Object? subject = null, 79 - }) { 80 - return _then( 81 - _value.copyWith( 82 - accessToken: null == accessToken 83 - ? _value.accessToken 84 - : accessToken // ignore: cast_nullable_to_non_nullable 85 - as String, 86 - tokenType: null == tokenType 87 - ? _value.tokenType 88 - : tokenType // ignore: cast_nullable_to_non_nullable 89 - as String, 90 - expiresAt: null == expiresAt 91 - ? _value.expiresAt 92 - : expiresAt // ignore: cast_nullable_to_non_nullable 93 - as DateTime, 94 - dpopJwk: null == dpopJwk 95 - ? _value.dpopJwk 96 - : dpopJwk // ignore: cast_nullable_to_non_nullable 97 - as JsonWebKey, 98 - issuer: null == issuer 99 - ? _value.issuer 100 - : issuer // ignore: cast_nullable_to_non_nullable 101 - as String, 102 - subject: null == subject 103 - ? _value.subject 104 - : subject // ignore: cast_nullable_to_non_nullable 105 - as String, 106 - ) 107 - as $Val, 108 - ); 109 - } 110 - } 111 - 112 - /// @nodoc 113 - abstract class _$$AtprotoSessionImplCopyWith<$Res> 114 - implements $AtprotoSessionCopyWith<$Res> { 115 - factory _$$AtprotoSessionImplCopyWith( 116 - _$AtprotoSessionImpl value, 117 - $Res Function(_$AtprotoSessionImpl) then, 118 - ) = __$$AtprotoSessionImplCopyWithImpl<$Res>; 119 - @override 120 - @useResult 121 - $Res call({ 122 - String accessToken, 123 - String tokenType, 124 - DateTime expiresAt, 125 - JsonWebKey dpopJwk, 126 - String issuer, 127 - String subject, 128 - }); 129 - } 130 - 131 - /// @nodoc 132 - class __$$AtprotoSessionImplCopyWithImpl<$Res> 133 - extends _$AtprotoSessionCopyWithImpl<$Res, _$AtprotoSessionImpl> 134 - implements _$$AtprotoSessionImplCopyWith<$Res> { 135 - __$$AtprotoSessionImplCopyWithImpl( 136 - _$AtprotoSessionImpl _value, 137 - $Res Function(_$AtprotoSessionImpl) _then, 138 - ) : super(_value, _then); 139 - 140 - /// Create a copy of AtprotoSession 141 - /// with the given fields replaced by the non-null parameter values. 142 - @pragma('vm:prefer-inline') 143 - @override 144 - $Res call({ 145 - Object? accessToken = null, 146 - Object? tokenType = null, 147 - Object? expiresAt = null, 148 - Object? dpopJwk = null, 149 - Object? issuer = null, 150 - Object? subject = null, 151 - }) { 152 - return _then( 153 - _$AtprotoSessionImpl( 154 - accessToken: null == accessToken 155 - ? _value.accessToken 156 - : accessToken // ignore: cast_nullable_to_non_nullable 157 - as String, 158 - tokenType: null == tokenType 159 - ? _value.tokenType 160 - : tokenType // ignore: cast_nullable_to_non_nullable 161 - as String, 162 - expiresAt: null == expiresAt 163 - ? _value.expiresAt 164 - : expiresAt // ignore: cast_nullable_to_non_nullable 165 - as DateTime, 166 - dpopJwk: null == dpopJwk 167 - ? _value.dpopJwk 168 - : dpopJwk // ignore: cast_nullable_to_non_nullable 169 - as JsonWebKey, 170 - issuer: null == issuer 171 - ? _value.issuer 172 - : issuer // ignore: cast_nullable_to_non_nullable 173 - as String, 174 - subject: null == subject 175 - ? _value.subject 176 - : subject // ignore: cast_nullable_to_non_nullable 177 - as String, 178 - ), 179 - ); 180 - } 181 - } 182 - 183 - /// @nodoc 184 - @JsonSerializable() 185 - class _$AtprotoSessionImpl implements _AtprotoSession { 186 - const _$AtprotoSessionImpl({ 187 - required this.accessToken, 188 - required this.tokenType, 189 - required this.expiresAt, 190 - required this.dpopJwk, 191 - required this.issuer, 192 - required this.subject, 193 - }); 194 - 195 - factory _$AtprotoSessionImpl.fromJson(Map<String, dynamic> json) => 196 - _$$AtprotoSessionImplFromJson(json); 197 - 198 - @override 199 - final String accessToken; 200 - @override 201 - final String tokenType; 202 - @override 203 - final DateTime expiresAt; 204 - @override 205 - final JsonWebKey dpopJwk; 206 - @override 207 - final String issuer; 208 - @override 209 - final String subject; 210 - 211 - @override 212 - String toString() { 213 - return 'AtprotoSession(accessToken: $accessToken, tokenType: $tokenType, expiresAt: $expiresAt, dpopJwk: $dpopJwk, issuer: $issuer, subject: $subject)'; 214 - } 215 - 216 - @override 217 - bool operator ==(Object other) { 218 - return identical(this, other) || 219 - (other.runtimeType == runtimeType && 220 - other is _$AtprotoSessionImpl && 221 - (identical(other.accessToken, accessToken) || 222 - other.accessToken == accessToken) && 223 - (identical(other.tokenType, tokenType) || 224 - other.tokenType == tokenType) && 225 - (identical(other.expiresAt, expiresAt) || 226 - other.expiresAt == expiresAt) && 227 - (identical(other.dpopJwk, dpopJwk) || other.dpopJwk == dpopJwk) && 228 - (identical(other.issuer, issuer) || other.issuer == issuer) && 229 - (identical(other.subject, subject) || other.subject == subject)); 230 - } 231 - 232 - @JsonKey(includeFromJson: false, includeToJson: false) 233 - @override 234 - int get hashCode => Object.hash( 235 - runtimeType, 236 - accessToken, 237 - tokenType, 238 - expiresAt, 239 - dpopJwk, 240 - issuer, 241 - subject, 242 - ); 243 - 244 - /// Create a copy of AtprotoSession 245 - /// with the given fields replaced by the non-null parameter values. 246 - @JsonKey(includeFromJson: false, includeToJson: false) 247 - @override 248 - @pragma('vm:prefer-inline') 249 - _$$AtprotoSessionImplCopyWith<_$AtprotoSessionImpl> get copyWith => 250 - __$$AtprotoSessionImplCopyWithImpl<_$AtprotoSessionImpl>( 251 - this, 252 - _$identity, 253 - ); 254 - 255 - @override 256 - Map<String, dynamic> toJson() { 257 - return _$$AtprotoSessionImplToJson(this); 258 - } 259 - } 260 - 261 - abstract class _AtprotoSession implements AtprotoSession { 262 - const factory _AtprotoSession({ 263 - required final String accessToken, 264 - required final String tokenType, 265 - required final DateTime expiresAt, 266 - required final JsonWebKey dpopJwk, 267 - required final String issuer, 268 - required final String subject, 269 - }) = _$AtprotoSessionImpl; 270 - 271 - factory _AtprotoSession.fromJson(Map<String, dynamic> json) = 272 - _$AtprotoSessionImpl.fromJson; 273 - 274 - @override 275 - String get accessToken; 276 - @override 277 - String get tokenType; 278 - @override 279 - DateTime get expiresAt; 280 - @override 281 - JsonWebKey get dpopJwk; 282 - @override 283 - String get issuer; 284 - @override 285 - String get subject; 286 - 287 - /// Create a copy of AtprotoSession 288 - /// with the given fields replaced by the non-null parameter values. 289 - @override 290 - @JsonKey(includeFromJson: false, includeToJson: false) 291 - _$$AtprotoSessionImplCopyWith<_$AtprotoSessionImpl> get copyWith => 292 - throw _privateConstructorUsedError; 293 - }
···
-28
lib/models/atproto_session.g.dart
··· 1 - // GENERATED CODE - DO NOT MODIFY BY HAND 2 - 3 - part of 'atproto_session.dart'; 4 - 5 - // ************************************************************************** 6 - // JsonSerializableGenerator 7 - // ************************************************************************** 8 - 9 - _$AtprotoSessionImpl _$$AtprotoSessionImplFromJson(Map<String, dynamic> json) => 10 - _$AtprotoSessionImpl( 11 - accessToken: json['accessToken'] as String, 12 - tokenType: json['tokenType'] as String, 13 - expiresAt: DateTime.parse(json['expiresAt'] as String), 14 - dpopJwk: JsonWebKey.fromJson(json['dpopJwk'] as Map<String, dynamic>), 15 - issuer: json['issuer'] as String, 16 - subject: json['subject'] as String, 17 - ); 18 - 19 - Map<String, dynamic> _$$AtprotoSessionImplToJson( 20 - _$AtprotoSessionImpl instance, 21 - ) => <String, dynamic>{ 22 - 'accessToken': instance.accessToken, 23 - 'tokenType': instance.tokenType, 24 - 'expiresAt': instance.expiresAt.toIso8601String(), 25 - 'dpopJwk': instance.dpopJwk, 26 - 'issuer': instance.issuer, 27 - 'subject': instance.subject, 28 - };
···
+2
lib/models/gallery_photo.dart
··· 2 3 import 'aspect_ratio.dart'; 4 import 'gallery_state.dart'; 5 6 part 'gallery_photo.freezed.dart'; 7 part 'gallery_photo.g.dart'; ··· 16 String? alt, 17 AspectRatio? aspectRatio, 18 GalleryState? gallery, 19 }) = _GalleryPhoto; 20 21 factory GalleryPhoto.fromJson(Map<String, dynamic> json) => _$GalleryPhotoFromJson(json);
··· 2 3 import 'aspect_ratio.dart'; 4 import 'gallery_state.dart'; 5 + import 'photo_exif.dart'; 6 7 part 'gallery_photo.freezed.dart'; 8 part 'gallery_photo.g.dart'; ··· 17 String? alt, 18 AspectRatio? aspectRatio, 19 GalleryState? gallery, 20 + PhotoExif? exif, 21 }) = _GalleryPhoto; 22 23 factory GalleryPhoto.fromJson(Map<String, dynamic> json) => _$GalleryPhotoFromJson(json);
+40 -2
lib/models/gallery_photo.freezed.dart
··· 28 String? get alt => throw _privateConstructorUsedError; 29 AspectRatio? get aspectRatio => throw _privateConstructorUsedError; 30 GalleryState? get gallery => throw _privateConstructorUsedError; 31 32 /// Serializes this GalleryPhoto to a JSON map. 33 Map<String, dynamic> toJson() => throw _privateConstructorUsedError; ··· 54 String? alt, 55 AspectRatio? aspectRatio, 56 GalleryState? gallery, 57 }); 58 59 $AspectRatioCopyWith<$Res>? get aspectRatio; 60 $GalleryStateCopyWith<$Res>? get gallery; 61 } 62 63 /// @nodoc ··· 82 Object? alt = freezed, 83 Object? aspectRatio = freezed, 84 Object? gallery = freezed, 85 }) { 86 return _then( 87 _value.copyWith( ··· 113 ? _value.gallery 114 : gallery // ignore: cast_nullable_to_non_nullable 115 as GalleryState?, 116 ) 117 as $Val, 118 ); ··· 145 return _then(_value.copyWith(gallery: value) as $Val); 146 }); 147 } 148 } 149 150 /// @nodoc ··· 164 String? alt, 165 AspectRatio? aspectRatio, 166 GalleryState? gallery, 167 }); 168 169 @override 170 $AspectRatioCopyWith<$Res>? get aspectRatio; 171 @override 172 $GalleryStateCopyWith<$Res>? get gallery; 173 } 174 175 /// @nodoc ··· 193 Object? alt = freezed, 194 Object? aspectRatio = freezed, 195 Object? gallery = freezed, 196 }) { 197 return _then( 198 _$GalleryPhotoImpl( ··· 224 ? _value.gallery 225 : gallery // ignore: cast_nullable_to_non_nullable 226 as GalleryState?, 227 ), 228 ); 229 } ··· 240 this.alt, 241 this.aspectRatio, 242 this.gallery, 243 }); 244 245 factory _$GalleryPhotoImpl.fromJson(Map<String, dynamic> json) => ··· 259 final AspectRatio? aspectRatio; 260 @override 261 final GalleryState? gallery; 262 263 @override 264 String toString() { 265 - return 'GalleryPhoto(uri: $uri, cid: $cid, thumb: $thumb, fullsize: $fullsize, alt: $alt, aspectRatio: $aspectRatio, gallery: $gallery)'; 266 } 267 268 @override ··· 278 (identical(other.alt, alt) || other.alt == alt) && 279 (identical(other.aspectRatio, aspectRatio) || 280 other.aspectRatio == aspectRatio) && 281 - (identical(other.gallery, gallery) || other.gallery == gallery)); 282 } 283 284 @JsonKey(includeFromJson: false, includeToJson: false) ··· 292 alt, 293 aspectRatio, 294 gallery, 295 ); 296 297 /// Create a copy of GalleryPhoto ··· 317 final String? alt, 318 final AspectRatio? aspectRatio, 319 final GalleryState? gallery, 320 }) = _$GalleryPhotoImpl; 321 322 factory _GalleryPhoto.fromJson(Map<String, dynamic> json) = ··· 336 AspectRatio? get aspectRatio; 337 @override 338 GalleryState? get gallery; 339 340 /// Create a copy of GalleryPhoto 341 /// with the given fields replaced by the non-null parameter values.
··· 28 String? get alt => throw _privateConstructorUsedError; 29 AspectRatio? get aspectRatio => throw _privateConstructorUsedError; 30 GalleryState? get gallery => throw _privateConstructorUsedError; 31 + PhotoExif? get exif => throw _privateConstructorUsedError; 32 33 /// Serializes this GalleryPhoto to a JSON map. 34 Map<String, dynamic> toJson() => throw _privateConstructorUsedError; ··· 55 String? alt, 56 AspectRatio? aspectRatio, 57 GalleryState? gallery, 58 + PhotoExif? exif, 59 }); 60 61 $AspectRatioCopyWith<$Res>? get aspectRatio; 62 $GalleryStateCopyWith<$Res>? get gallery; 63 + $PhotoExifCopyWith<$Res>? get exif; 64 } 65 66 /// @nodoc ··· 85 Object? alt = freezed, 86 Object? aspectRatio = freezed, 87 Object? gallery = freezed, 88 + Object? exif = freezed, 89 }) { 90 return _then( 91 _value.copyWith( ··· 117 ? _value.gallery 118 : gallery // ignore: cast_nullable_to_non_nullable 119 as GalleryState?, 120 + exif: freezed == exif 121 + ? _value.exif 122 + : exif // ignore: cast_nullable_to_non_nullable 123 + as PhotoExif?, 124 ) 125 as $Val, 126 ); ··· 153 return _then(_value.copyWith(gallery: value) as $Val); 154 }); 155 } 156 + 157 + /// Create a copy of GalleryPhoto 158 + /// with the given fields replaced by the non-null parameter values. 159 + @override 160 + @pragma('vm:prefer-inline') 161 + $PhotoExifCopyWith<$Res>? get exif { 162 + if (_value.exif == null) { 163 + return null; 164 + } 165 + 166 + return $PhotoExifCopyWith<$Res>(_value.exif!, (value) { 167 + return _then(_value.copyWith(exif: value) as $Val); 168 + }); 169 + } 170 } 171 172 /// @nodoc ··· 186 String? alt, 187 AspectRatio? aspectRatio, 188 GalleryState? gallery, 189 + PhotoExif? exif, 190 }); 191 192 @override 193 $AspectRatioCopyWith<$Res>? get aspectRatio; 194 @override 195 $GalleryStateCopyWith<$Res>? get gallery; 196 + @override 197 + $PhotoExifCopyWith<$Res>? get exif; 198 } 199 200 /// @nodoc ··· 218 Object? alt = freezed, 219 Object? aspectRatio = freezed, 220 Object? gallery = freezed, 221 + Object? exif = freezed, 222 }) { 223 return _then( 224 _$GalleryPhotoImpl( ··· 250 ? _value.gallery 251 : gallery // ignore: cast_nullable_to_non_nullable 252 as GalleryState?, 253 + exif: freezed == exif 254 + ? _value.exif 255 + : exif // ignore: cast_nullable_to_non_nullable 256 + as PhotoExif?, 257 ), 258 ); 259 } ··· 270 this.alt, 271 this.aspectRatio, 272 this.gallery, 273 + this.exif, 274 }); 275 276 factory _$GalleryPhotoImpl.fromJson(Map<String, dynamic> json) => ··· 290 final AspectRatio? aspectRatio; 291 @override 292 final GalleryState? gallery; 293 + @override 294 + final PhotoExif? exif; 295 296 @override 297 String toString() { 298 + return 'GalleryPhoto(uri: $uri, cid: $cid, thumb: $thumb, fullsize: $fullsize, alt: $alt, aspectRatio: $aspectRatio, gallery: $gallery, exif: $exif)'; 299 } 300 301 @override ··· 311 (identical(other.alt, alt) || other.alt == alt) && 312 (identical(other.aspectRatio, aspectRatio) || 313 other.aspectRatio == aspectRatio) && 314 + (identical(other.gallery, gallery) || other.gallery == gallery) && 315 + (identical(other.exif, exif) || other.exif == exif)); 316 } 317 318 @JsonKey(includeFromJson: false, includeToJson: false) ··· 326 alt, 327 aspectRatio, 328 gallery, 329 + exif, 330 ); 331 332 /// Create a copy of GalleryPhoto ··· 352 final String? alt, 353 final AspectRatio? aspectRatio, 354 final GalleryState? gallery, 355 + final PhotoExif? exif, 356 }) = _$GalleryPhotoImpl; 357 358 factory _GalleryPhoto.fromJson(Map<String, dynamic> json) = ··· 372 AspectRatio? get aspectRatio; 373 @override 374 GalleryState? get gallery; 375 + @override 376 + PhotoExif? get exif; 377 378 /// Create a copy of GalleryPhoto 379 /// with the given fields replaced by the non-null parameter values.
+4
lib/models/gallery_photo.g.dart
··· 19 gallery: json['gallery'] == null 20 ? null 21 : GalleryState.fromJson(json['gallery'] as Map<String, dynamic>), 22 ); 23 24 Map<String, dynamic> _$$GalleryPhotoImplToJson(_$GalleryPhotoImpl instance) => ··· 30 'alt': instance.alt, 31 'aspectRatio': instance.aspectRatio, 32 'gallery': instance.gallery, 33 };
··· 19 gallery: json['gallery'] == null 20 ? null 21 : GalleryState.fromJson(json['gallery'] as Map<String, dynamic>), 22 + exif: json['exif'] == null 23 + ? null 24 + : PhotoExif.fromJson(json['exif'] as Map<String, dynamic>), 25 ); 26 27 Map<String, dynamic> _$$GalleryPhotoImplToJson(_$GalleryPhotoImpl instance) => ··· 33 'alt': instance.alt, 34 'aspectRatio': instance.aspectRatio, 35 'gallery': instance.gallery, 36 + 'exif': instance.exif, 37 };
+26
lib/models/photo_exif.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'photo_exif.freezed.dart'; 4 + part 'photo_exif.g.dart'; 5 + 6 + @freezed 7 + class PhotoExif with _$PhotoExif { 8 + const factory PhotoExif({ 9 + required String photo, // at-uri 10 + required String createdAt, // datetime 11 + String? uri, // at-uri 12 + String? cid, // cid 13 + String? dateTimeOriginal, // datetime 14 + String? exposureTime, 15 + String? fNumber, 16 + String? flash, 17 + String? focalLengthIn35mmFormat, 18 + int? iSO, 19 + String? lensMake, 20 + String? lensModel, 21 + String? make, 22 + String? model, 23 + }) = _PhotoExif; 24 + 25 + factory PhotoExif.fromJson(Map<String, dynamic> json) => _$PhotoExifFromJson(json); 26 + }
+468
lib/models/photo_exif.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'photo_exif.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + PhotoExif _$PhotoExifFromJson(Map<String, dynamic> json) { 19 + return _PhotoExif.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$PhotoExif { 24 + String get photo => throw _privateConstructorUsedError; // at-uri 25 + String get createdAt => throw _privateConstructorUsedError; // datetime 26 + String? get uri => throw _privateConstructorUsedError; // at-uri 27 + String? get cid => throw _privateConstructorUsedError; // cid 28 + String? get dateTimeOriginal => 29 + throw _privateConstructorUsedError; // datetime 30 + String? get exposureTime => throw _privateConstructorUsedError; 31 + String? get fNumber => throw _privateConstructorUsedError; 32 + String? get flash => throw _privateConstructorUsedError; 33 + String? get focalLengthIn35mmFormat => throw _privateConstructorUsedError; 34 + int? get iSO => throw _privateConstructorUsedError; 35 + String? get lensMake => throw _privateConstructorUsedError; 36 + String? get lensModel => throw _privateConstructorUsedError; 37 + String? get make => throw _privateConstructorUsedError; 38 + String? get model => throw _privateConstructorUsedError; 39 + 40 + /// Serializes this PhotoExif to a JSON map. 41 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 42 + 43 + /// Create a copy of PhotoExif 44 + /// with the given fields replaced by the non-null parameter values. 45 + @JsonKey(includeFromJson: false, includeToJson: false) 46 + $PhotoExifCopyWith<PhotoExif> get copyWith => 47 + throw _privateConstructorUsedError; 48 + } 49 + 50 + /// @nodoc 51 + abstract class $PhotoExifCopyWith<$Res> { 52 + factory $PhotoExifCopyWith(PhotoExif value, $Res Function(PhotoExif) then) = 53 + _$PhotoExifCopyWithImpl<$Res, PhotoExif>; 54 + @useResult 55 + $Res call({ 56 + String photo, 57 + String createdAt, 58 + String? uri, 59 + String? cid, 60 + String? dateTimeOriginal, 61 + String? exposureTime, 62 + String? fNumber, 63 + String? flash, 64 + String? focalLengthIn35mmFormat, 65 + int? iSO, 66 + String? lensMake, 67 + String? lensModel, 68 + String? make, 69 + String? model, 70 + }); 71 + } 72 + 73 + /// @nodoc 74 + class _$PhotoExifCopyWithImpl<$Res, $Val extends PhotoExif> 75 + implements $PhotoExifCopyWith<$Res> { 76 + _$PhotoExifCopyWithImpl(this._value, this._then); 77 + 78 + // ignore: unused_field 79 + final $Val _value; 80 + // ignore: unused_field 81 + final $Res Function($Val) _then; 82 + 83 + /// Create a copy of PhotoExif 84 + /// with the given fields replaced by the non-null parameter values. 85 + @pragma('vm:prefer-inline') 86 + @override 87 + $Res call({ 88 + Object? photo = null, 89 + Object? createdAt = null, 90 + Object? uri = freezed, 91 + Object? cid = freezed, 92 + Object? dateTimeOriginal = freezed, 93 + Object? exposureTime = freezed, 94 + Object? fNumber = freezed, 95 + Object? flash = freezed, 96 + Object? focalLengthIn35mmFormat = freezed, 97 + Object? iSO = freezed, 98 + Object? lensMake = freezed, 99 + Object? lensModel = freezed, 100 + Object? make = freezed, 101 + Object? model = freezed, 102 + }) { 103 + return _then( 104 + _value.copyWith( 105 + photo: null == photo 106 + ? _value.photo 107 + : photo // ignore: cast_nullable_to_non_nullable 108 + as String, 109 + createdAt: null == createdAt 110 + ? _value.createdAt 111 + : createdAt // ignore: cast_nullable_to_non_nullable 112 + as String, 113 + uri: freezed == uri 114 + ? _value.uri 115 + : uri // ignore: cast_nullable_to_non_nullable 116 + as String?, 117 + cid: freezed == cid 118 + ? _value.cid 119 + : cid // ignore: cast_nullable_to_non_nullable 120 + as String?, 121 + dateTimeOriginal: freezed == dateTimeOriginal 122 + ? _value.dateTimeOriginal 123 + : dateTimeOriginal // ignore: cast_nullable_to_non_nullable 124 + as String?, 125 + exposureTime: freezed == exposureTime 126 + ? _value.exposureTime 127 + : exposureTime // ignore: cast_nullable_to_non_nullable 128 + as String?, 129 + fNumber: freezed == fNumber 130 + ? _value.fNumber 131 + : fNumber // ignore: cast_nullable_to_non_nullable 132 + as String?, 133 + flash: freezed == flash 134 + ? _value.flash 135 + : flash // ignore: cast_nullable_to_non_nullable 136 + as String?, 137 + focalLengthIn35mmFormat: freezed == focalLengthIn35mmFormat 138 + ? _value.focalLengthIn35mmFormat 139 + : focalLengthIn35mmFormat // ignore: cast_nullable_to_non_nullable 140 + as String?, 141 + iSO: freezed == iSO 142 + ? _value.iSO 143 + : iSO // ignore: cast_nullable_to_non_nullable 144 + as int?, 145 + lensMake: freezed == lensMake 146 + ? _value.lensMake 147 + : lensMake // ignore: cast_nullable_to_non_nullable 148 + as String?, 149 + lensModel: freezed == lensModel 150 + ? _value.lensModel 151 + : lensModel // ignore: cast_nullable_to_non_nullable 152 + as String?, 153 + make: freezed == make 154 + ? _value.make 155 + : make // ignore: cast_nullable_to_non_nullable 156 + as String?, 157 + model: freezed == model 158 + ? _value.model 159 + : model // ignore: cast_nullable_to_non_nullable 160 + as String?, 161 + ) 162 + as $Val, 163 + ); 164 + } 165 + } 166 + 167 + /// @nodoc 168 + abstract class _$$PhotoExifImplCopyWith<$Res> 169 + implements $PhotoExifCopyWith<$Res> { 170 + factory _$$PhotoExifImplCopyWith( 171 + _$PhotoExifImpl value, 172 + $Res Function(_$PhotoExifImpl) then, 173 + ) = __$$PhotoExifImplCopyWithImpl<$Res>; 174 + @override 175 + @useResult 176 + $Res call({ 177 + String photo, 178 + String createdAt, 179 + String? uri, 180 + String? cid, 181 + String? dateTimeOriginal, 182 + String? exposureTime, 183 + String? fNumber, 184 + String? flash, 185 + String? focalLengthIn35mmFormat, 186 + int? iSO, 187 + String? lensMake, 188 + String? lensModel, 189 + String? make, 190 + String? model, 191 + }); 192 + } 193 + 194 + /// @nodoc 195 + class __$$PhotoExifImplCopyWithImpl<$Res> 196 + extends _$PhotoExifCopyWithImpl<$Res, _$PhotoExifImpl> 197 + implements _$$PhotoExifImplCopyWith<$Res> { 198 + __$$PhotoExifImplCopyWithImpl( 199 + _$PhotoExifImpl _value, 200 + $Res Function(_$PhotoExifImpl) _then, 201 + ) : super(_value, _then); 202 + 203 + /// Create a copy of PhotoExif 204 + /// with the given fields replaced by the non-null parameter values. 205 + @pragma('vm:prefer-inline') 206 + @override 207 + $Res call({ 208 + Object? photo = null, 209 + Object? createdAt = null, 210 + Object? uri = freezed, 211 + Object? cid = freezed, 212 + Object? dateTimeOriginal = freezed, 213 + Object? exposureTime = freezed, 214 + Object? fNumber = freezed, 215 + Object? flash = freezed, 216 + Object? focalLengthIn35mmFormat = freezed, 217 + Object? iSO = freezed, 218 + Object? lensMake = freezed, 219 + Object? lensModel = freezed, 220 + Object? make = freezed, 221 + Object? model = freezed, 222 + }) { 223 + return _then( 224 + _$PhotoExifImpl( 225 + photo: null == photo 226 + ? _value.photo 227 + : photo // ignore: cast_nullable_to_non_nullable 228 + as String, 229 + createdAt: null == createdAt 230 + ? _value.createdAt 231 + : createdAt // ignore: cast_nullable_to_non_nullable 232 + as String, 233 + uri: freezed == uri 234 + ? _value.uri 235 + : uri // ignore: cast_nullable_to_non_nullable 236 + as String?, 237 + cid: freezed == cid 238 + ? _value.cid 239 + : cid // ignore: cast_nullable_to_non_nullable 240 + as String?, 241 + dateTimeOriginal: freezed == dateTimeOriginal 242 + ? _value.dateTimeOriginal 243 + : dateTimeOriginal // ignore: cast_nullable_to_non_nullable 244 + as String?, 245 + exposureTime: freezed == exposureTime 246 + ? _value.exposureTime 247 + : exposureTime // ignore: cast_nullable_to_non_nullable 248 + as String?, 249 + fNumber: freezed == fNumber 250 + ? _value.fNumber 251 + : fNumber // ignore: cast_nullable_to_non_nullable 252 + as String?, 253 + flash: freezed == flash 254 + ? _value.flash 255 + : flash // ignore: cast_nullable_to_non_nullable 256 + as String?, 257 + focalLengthIn35mmFormat: freezed == focalLengthIn35mmFormat 258 + ? _value.focalLengthIn35mmFormat 259 + : focalLengthIn35mmFormat // ignore: cast_nullable_to_non_nullable 260 + as String?, 261 + iSO: freezed == iSO 262 + ? _value.iSO 263 + : iSO // ignore: cast_nullable_to_non_nullable 264 + as int?, 265 + lensMake: freezed == lensMake 266 + ? _value.lensMake 267 + : lensMake // ignore: cast_nullable_to_non_nullable 268 + as String?, 269 + lensModel: freezed == lensModel 270 + ? _value.lensModel 271 + : lensModel // ignore: cast_nullable_to_non_nullable 272 + as String?, 273 + make: freezed == make 274 + ? _value.make 275 + : make // ignore: cast_nullable_to_non_nullable 276 + as String?, 277 + model: freezed == model 278 + ? _value.model 279 + : model // ignore: cast_nullable_to_non_nullable 280 + as String?, 281 + ), 282 + ); 283 + } 284 + } 285 + 286 + /// @nodoc 287 + @JsonSerializable() 288 + class _$PhotoExifImpl implements _PhotoExif { 289 + const _$PhotoExifImpl({ 290 + required this.photo, 291 + required this.createdAt, 292 + this.uri, 293 + this.cid, 294 + this.dateTimeOriginal, 295 + this.exposureTime, 296 + this.fNumber, 297 + this.flash, 298 + this.focalLengthIn35mmFormat, 299 + this.iSO, 300 + this.lensMake, 301 + this.lensModel, 302 + this.make, 303 + this.model, 304 + }); 305 + 306 + factory _$PhotoExifImpl.fromJson(Map<String, dynamic> json) => 307 + _$$PhotoExifImplFromJson(json); 308 + 309 + @override 310 + final String photo; 311 + // at-uri 312 + @override 313 + final String createdAt; 314 + // datetime 315 + @override 316 + final String? uri; 317 + // at-uri 318 + @override 319 + final String? cid; 320 + // cid 321 + @override 322 + final String? dateTimeOriginal; 323 + // datetime 324 + @override 325 + final String? exposureTime; 326 + @override 327 + final String? fNumber; 328 + @override 329 + final String? flash; 330 + @override 331 + final String? focalLengthIn35mmFormat; 332 + @override 333 + final int? iSO; 334 + @override 335 + final String? lensMake; 336 + @override 337 + final String? lensModel; 338 + @override 339 + final String? make; 340 + @override 341 + final String? model; 342 + 343 + @override 344 + String toString() { 345 + return 'PhotoExif(photo: $photo, createdAt: $createdAt, uri: $uri, cid: $cid, dateTimeOriginal: $dateTimeOriginal, exposureTime: $exposureTime, fNumber: $fNumber, flash: $flash, focalLengthIn35mmFormat: $focalLengthIn35mmFormat, iSO: $iSO, lensMake: $lensMake, lensModel: $lensModel, make: $make, model: $model)'; 346 + } 347 + 348 + @override 349 + bool operator ==(Object other) { 350 + return identical(this, other) || 351 + (other.runtimeType == runtimeType && 352 + other is _$PhotoExifImpl && 353 + (identical(other.photo, photo) || other.photo == photo) && 354 + (identical(other.createdAt, createdAt) || 355 + other.createdAt == createdAt) && 356 + (identical(other.uri, uri) || other.uri == uri) && 357 + (identical(other.cid, cid) || other.cid == cid) && 358 + (identical(other.dateTimeOriginal, dateTimeOriginal) || 359 + other.dateTimeOriginal == dateTimeOriginal) && 360 + (identical(other.exposureTime, exposureTime) || 361 + other.exposureTime == exposureTime) && 362 + (identical(other.fNumber, fNumber) || other.fNumber == fNumber) && 363 + (identical(other.flash, flash) || other.flash == flash) && 364 + (identical( 365 + other.focalLengthIn35mmFormat, 366 + focalLengthIn35mmFormat, 367 + ) || 368 + other.focalLengthIn35mmFormat == focalLengthIn35mmFormat) && 369 + (identical(other.iSO, iSO) || other.iSO == iSO) && 370 + (identical(other.lensMake, lensMake) || 371 + other.lensMake == lensMake) && 372 + (identical(other.lensModel, lensModel) || 373 + other.lensModel == lensModel) && 374 + (identical(other.make, make) || other.make == make) && 375 + (identical(other.model, model) || other.model == model)); 376 + } 377 + 378 + @JsonKey(includeFromJson: false, includeToJson: false) 379 + @override 380 + int get hashCode => Object.hash( 381 + runtimeType, 382 + photo, 383 + createdAt, 384 + uri, 385 + cid, 386 + dateTimeOriginal, 387 + exposureTime, 388 + fNumber, 389 + flash, 390 + focalLengthIn35mmFormat, 391 + iSO, 392 + lensMake, 393 + lensModel, 394 + make, 395 + model, 396 + ); 397 + 398 + /// Create a copy of PhotoExif 399 + /// with the given fields replaced by the non-null parameter values. 400 + @JsonKey(includeFromJson: false, includeToJson: false) 401 + @override 402 + @pragma('vm:prefer-inline') 403 + _$$PhotoExifImplCopyWith<_$PhotoExifImpl> get copyWith => 404 + __$$PhotoExifImplCopyWithImpl<_$PhotoExifImpl>(this, _$identity); 405 + 406 + @override 407 + Map<String, dynamic> toJson() { 408 + return _$$PhotoExifImplToJson(this); 409 + } 410 + } 411 + 412 + abstract class _PhotoExif implements PhotoExif { 413 + const factory _PhotoExif({ 414 + required final String photo, 415 + required final String createdAt, 416 + final String? uri, 417 + final String? cid, 418 + final String? dateTimeOriginal, 419 + final String? exposureTime, 420 + final String? fNumber, 421 + final String? flash, 422 + final String? focalLengthIn35mmFormat, 423 + final int? iSO, 424 + final String? lensMake, 425 + final String? lensModel, 426 + final String? make, 427 + final String? model, 428 + }) = _$PhotoExifImpl; 429 + 430 + factory _PhotoExif.fromJson(Map<String, dynamic> json) = 431 + _$PhotoExifImpl.fromJson; 432 + 433 + @override 434 + String get photo; // at-uri 435 + @override 436 + String get createdAt; // datetime 437 + @override 438 + String? get uri; // at-uri 439 + @override 440 + String? get cid; // cid 441 + @override 442 + String? get dateTimeOriginal; // datetime 443 + @override 444 + String? get exposureTime; 445 + @override 446 + String? get fNumber; 447 + @override 448 + String? get flash; 449 + @override 450 + String? get focalLengthIn35mmFormat; 451 + @override 452 + int? get iSO; 453 + @override 454 + String? get lensMake; 455 + @override 456 + String? get lensModel; 457 + @override 458 + String? get make; 459 + @override 460 + String? get model; 461 + 462 + /// Create a copy of PhotoExif 463 + /// with the given fields replaced by the non-null parameter values. 464 + @override 465 + @JsonKey(includeFromJson: false, includeToJson: false) 466 + _$$PhotoExifImplCopyWith<_$PhotoExifImpl> get copyWith => 467 + throw _privateConstructorUsedError; 468 + }
+43
lib/models/photo_exif.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'photo_exif.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$PhotoExifImpl _$$PhotoExifImplFromJson(Map<String, dynamic> json) => 10 + _$PhotoExifImpl( 11 + photo: json['photo'] as String, 12 + createdAt: json['createdAt'] as String, 13 + uri: json['uri'] as String?, 14 + cid: json['cid'] as String?, 15 + dateTimeOriginal: json['dateTimeOriginal'] as String?, 16 + exposureTime: json['exposureTime'] as String?, 17 + fNumber: json['fNumber'] as String?, 18 + flash: json['flash'] as String?, 19 + focalLengthIn35mmFormat: json['focalLengthIn35mmFormat'] as String?, 20 + iSO: (json['iSO'] as num?)?.toInt(), 21 + lensMake: json['lensMake'] as String?, 22 + lensModel: json['lensModel'] as String?, 23 + make: json['make'] as String?, 24 + model: json['model'] as String?, 25 + ); 26 + 27 + Map<String, dynamic> _$$PhotoExifImplToJson(_$PhotoExifImpl instance) => 28 + <String, dynamic>{ 29 + 'photo': instance.photo, 30 + 'createdAt': instance.createdAt, 31 + 'uri': instance.uri, 32 + 'cid': instance.cid, 33 + 'dateTimeOriginal': instance.dateTimeOriginal, 34 + 'exposureTime': instance.exposureTime, 35 + 'fNumber': instance.fNumber, 36 + 'flash': instance.flash, 37 + 'focalLengthIn35mmFormat': instance.focalLengthIn35mmFormat, 38 + 'iSO': instance.iSO, 39 + 'lensMake': instance.lensMake, 40 + 'lensModel': instance.lensModel, 41 + 'make': instance.make, 42 + 'model': instance.model, 43 + };
+15
lib/models/procedures/apply_alts_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + import 'apply_alts_update.dart'; 4 + 5 + part 'apply_alts_request.freezed.dart'; 6 + part 'apply_alts_request.g.dart'; 7 + 8 + /// Request to apply alt texts to photos in a gallery. 9 + /// Requires auth. 10 + @freezed 11 + class ApplyAltsRequest with _$ApplyAltsRequest { 12 + const factory ApplyAltsRequest({required List<ApplyAltsUpdate> writes}) = _ApplyAltsRequest; 13 + 14 + factory ApplyAltsRequest.fromJson(Map<String, dynamic> json) => _$ApplyAltsRequestFromJson(json); 15 + }
+179
lib/models/procedures/apply_alts_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'apply_alts_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + ApplyAltsRequest _$ApplyAltsRequestFromJson(Map<String, dynamic> json) { 19 + return _ApplyAltsRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$ApplyAltsRequest { 24 + List<ApplyAltsUpdate> get writes => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this ApplyAltsRequest to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of ApplyAltsRequest 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $ApplyAltsRequestCopyWith<ApplyAltsRequest> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $ApplyAltsRequestCopyWith<$Res> { 38 + factory $ApplyAltsRequestCopyWith( 39 + ApplyAltsRequest value, 40 + $Res Function(ApplyAltsRequest) then, 41 + ) = _$ApplyAltsRequestCopyWithImpl<$Res, ApplyAltsRequest>; 42 + @useResult 43 + $Res call({List<ApplyAltsUpdate> writes}); 44 + } 45 + 46 + /// @nodoc 47 + class _$ApplyAltsRequestCopyWithImpl<$Res, $Val extends ApplyAltsRequest> 48 + implements $ApplyAltsRequestCopyWith<$Res> { 49 + _$ApplyAltsRequestCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of ApplyAltsRequest 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? writes = null}) { 61 + return _then( 62 + _value.copyWith( 63 + writes: null == writes 64 + ? _value.writes 65 + : writes // ignore: cast_nullable_to_non_nullable 66 + as List<ApplyAltsUpdate>, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$ApplyAltsRequestImplCopyWith<$Res> 75 + implements $ApplyAltsRequestCopyWith<$Res> { 76 + factory _$$ApplyAltsRequestImplCopyWith( 77 + _$ApplyAltsRequestImpl value, 78 + $Res Function(_$ApplyAltsRequestImpl) then, 79 + ) = __$$ApplyAltsRequestImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({List<ApplyAltsUpdate> writes}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$ApplyAltsRequestImplCopyWithImpl<$Res> 87 + extends _$ApplyAltsRequestCopyWithImpl<$Res, _$ApplyAltsRequestImpl> 88 + implements _$$ApplyAltsRequestImplCopyWith<$Res> { 89 + __$$ApplyAltsRequestImplCopyWithImpl( 90 + _$ApplyAltsRequestImpl _value, 91 + $Res Function(_$ApplyAltsRequestImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of ApplyAltsRequest 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? writes = null}) { 99 + return _then( 100 + _$ApplyAltsRequestImpl( 101 + writes: null == writes 102 + ? _value._writes 103 + : writes // ignore: cast_nullable_to_non_nullable 104 + as List<ApplyAltsUpdate>, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$ApplyAltsRequestImpl implements _ApplyAltsRequest { 113 + const _$ApplyAltsRequestImpl({required final List<ApplyAltsUpdate> writes}) 114 + : _writes = writes; 115 + 116 + factory _$ApplyAltsRequestImpl.fromJson(Map<String, dynamic> json) => 117 + _$$ApplyAltsRequestImplFromJson(json); 118 + 119 + final List<ApplyAltsUpdate> _writes; 120 + @override 121 + List<ApplyAltsUpdate> get writes { 122 + if (_writes is EqualUnmodifiableListView) return _writes; 123 + // ignore: implicit_dynamic_type 124 + return EqualUnmodifiableListView(_writes); 125 + } 126 + 127 + @override 128 + String toString() { 129 + return 'ApplyAltsRequest(writes: $writes)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$ApplyAltsRequestImpl && 137 + const DeepCollectionEquality().equals(other._writes, _writes)); 138 + } 139 + 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + int get hashCode => 143 + Object.hash(runtimeType, const DeepCollectionEquality().hash(_writes)); 144 + 145 + /// Create a copy of ApplyAltsRequest 146 + /// with the given fields replaced by the non-null parameter values. 147 + @JsonKey(includeFromJson: false, includeToJson: false) 148 + @override 149 + @pragma('vm:prefer-inline') 150 + _$$ApplyAltsRequestImplCopyWith<_$ApplyAltsRequestImpl> get copyWith => 151 + __$$ApplyAltsRequestImplCopyWithImpl<_$ApplyAltsRequestImpl>( 152 + this, 153 + _$identity, 154 + ); 155 + 156 + @override 157 + Map<String, dynamic> toJson() { 158 + return _$$ApplyAltsRequestImplToJson(this); 159 + } 160 + } 161 + 162 + abstract class _ApplyAltsRequest implements ApplyAltsRequest { 163 + const factory _ApplyAltsRequest({ 164 + required final List<ApplyAltsUpdate> writes, 165 + }) = _$ApplyAltsRequestImpl; 166 + 167 + factory _ApplyAltsRequest.fromJson(Map<String, dynamic> json) = 168 + _$ApplyAltsRequestImpl.fromJson; 169 + 170 + @override 171 + List<ApplyAltsUpdate> get writes; 172 + 173 + /// Create a copy of ApplyAltsRequest 174 + /// with the given fields replaced by the non-null parameter values. 175 + @override 176 + @JsonKey(includeFromJson: false, includeToJson: false) 177 + _$$ApplyAltsRequestImplCopyWith<_$ApplyAltsRequestImpl> get copyWith => 178 + throw _privateConstructorUsedError; 179 + }
+19
lib/models/procedures/apply_alts_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'apply_alts_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$ApplyAltsRequestImpl _$$ApplyAltsRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$ApplyAltsRequestImpl( 12 + writes: (json['writes'] as List<dynamic>) 13 + .map((e) => ApplyAltsUpdate.fromJson(e as Map<String, dynamic>)) 14 + .toList(), 15 + ); 16 + 17 + Map<String, dynamic> _$$ApplyAltsRequestImplToJson( 18 + _$ApplyAltsRequestImpl instance, 19 + ) => <String, dynamic>{'writes': instance.writes};
+14
lib/models/procedures/apply_alts_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'apply_alts_response.freezed.dart'; 4 + part 'apply_alts_response.g.dart'; 5 + 6 + /// Response for applyAlts API. 7 + /// True if the writes were successfully applied. 8 + @freezed 9 + class ApplyAltsResponse with _$ApplyAltsResponse { 10 + const factory ApplyAltsResponse({required bool success}) = _ApplyAltsResponse; 11 + 12 + factory ApplyAltsResponse.fromJson(Map<String, dynamic> json) => 13 + _$ApplyAltsResponseFromJson(json); 14 + }
+171
lib/models/procedures/apply_alts_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'apply_alts_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + ApplyAltsResponse _$ApplyAltsResponseFromJson(Map<String, dynamic> json) { 19 + return _ApplyAltsResponse.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$ApplyAltsResponse { 24 + bool get success => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this ApplyAltsResponse to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of ApplyAltsResponse 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $ApplyAltsResponseCopyWith<ApplyAltsResponse> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $ApplyAltsResponseCopyWith<$Res> { 38 + factory $ApplyAltsResponseCopyWith( 39 + ApplyAltsResponse value, 40 + $Res Function(ApplyAltsResponse) then, 41 + ) = _$ApplyAltsResponseCopyWithImpl<$Res, ApplyAltsResponse>; 42 + @useResult 43 + $Res call({bool success}); 44 + } 45 + 46 + /// @nodoc 47 + class _$ApplyAltsResponseCopyWithImpl<$Res, $Val extends ApplyAltsResponse> 48 + implements $ApplyAltsResponseCopyWith<$Res> { 49 + _$ApplyAltsResponseCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of ApplyAltsResponse 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? success = null}) { 61 + return _then( 62 + _value.copyWith( 63 + success: null == success 64 + ? _value.success 65 + : success // ignore: cast_nullable_to_non_nullable 66 + as bool, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$ApplyAltsResponseImplCopyWith<$Res> 75 + implements $ApplyAltsResponseCopyWith<$Res> { 76 + factory _$$ApplyAltsResponseImplCopyWith( 77 + _$ApplyAltsResponseImpl value, 78 + $Res Function(_$ApplyAltsResponseImpl) then, 79 + ) = __$$ApplyAltsResponseImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({bool success}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$ApplyAltsResponseImplCopyWithImpl<$Res> 87 + extends _$ApplyAltsResponseCopyWithImpl<$Res, _$ApplyAltsResponseImpl> 88 + implements _$$ApplyAltsResponseImplCopyWith<$Res> { 89 + __$$ApplyAltsResponseImplCopyWithImpl( 90 + _$ApplyAltsResponseImpl _value, 91 + $Res Function(_$ApplyAltsResponseImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of ApplyAltsResponse 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? success = null}) { 99 + return _then( 100 + _$ApplyAltsResponseImpl( 101 + success: null == success 102 + ? _value.success 103 + : success // ignore: cast_nullable_to_non_nullable 104 + as bool, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$ApplyAltsResponseImpl implements _ApplyAltsResponse { 113 + const _$ApplyAltsResponseImpl({required this.success}); 114 + 115 + factory _$ApplyAltsResponseImpl.fromJson(Map<String, dynamic> json) => 116 + _$$ApplyAltsResponseImplFromJson(json); 117 + 118 + @override 119 + final bool success; 120 + 121 + @override 122 + String toString() { 123 + return 'ApplyAltsResponse(success: $success)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$ApplyAltsResponseImpl && 131 + (identical(other.success, success) || other.success == success)); 132 + } 133 + 134 + @JsonKey(includeFromJson: false, includeToJson: false) 135 + @override 136 + int get hashCode => Object.hash(runtimeType, success); 137 + 138 + /// Create a copy of ApplyAltsResponse 139 + /// with the given fields replaced by the non-null parameter values. 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + @pragma('vm:prefer-inline') 143 + _$$ApplyAltsResponseImplCopyWith<_$ApplyAltsResponseImpl> get copyWith => 144 + __$$ApplyAltsResponseImplCopyWithImpl<_$ApplyAltsResponseImpl>( 145 + this, 146 + _$identity, 147 + ); 148 + 149 + @override 150 + Map<String, dynamic> toJson() { 151 + return _$$ApplyAltsResponseImplToJson(this); 152 + } 153 + } 154 + 155 + abstract class _ApplyAltsResponse implements ApplyAltsResponse { 156 + const factory _ApplyAltsResponse({required final bool success}) = 157 + _$ApplyAltsResponseImpl; 158 + 159 + factory _ApplyAltsResponse.fromJson(Map<String, dynamic> json) = 160 + _$ApplyAltsResponseImpl.fromJson; 161 + 162 + @override 163 + bool get success; 164 + 165 + /// Create a copy of ApplyAltsResponse 166 + /// with the given fields replaced by the non-null parameter values. 167 + @override 168 + @JsonKey(includeFromJson: false, includeToJson: false) 169 + _$$ApplyAltsResponseImplCopyWith<_$ApplyAltsResponseImpl> get copyWith => 170 + throw _privateConstructorUsedError; 171 + }
+15
lib/models/procedures/apply_alts_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'apply_alts_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$ApplyAltsResponseImpl _$$ApplyAltsResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$ApplyAltsResponseImpl(success: json['success'] as bool); 12 + 13 + Map<String, dynamic> _$$ApplyAltsResponseImplToJson( 14 + _$ApplyAltsResponseImpl instance, 15 + ) => <String, dynamic>{'success': instance.success};
+13
lib/models/procedures/apply_alts_update.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'apply_alts_update.freezed.dart'; 4 + part 'apply_alts_update.g.dart'; 5 + 6 + /// Update alt text for a photo in a gallery. 7 + /// AT URI of the item to update and the alt text to apply. 8 + @freezed 9 + class ApplyAltsUpdate with _$ApplyAltsUpdate { 10 + const factory ApplyAltsUpdate({required String photoUri, required String alt}) = _ApplyAltsUpdate; 11 + 12 + factory ApplyAltsUpdate.fromJson(Map<String, dynamic> json) => _$ApplyAltsUpdateFromJson(json); 13 + }
+188
lib/models/procedures/apply_alts_update.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'apply_alts_update.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + ApplyAltsUpdate _$ApplyAltsUpdateFromJson(Map<String, dynamic> json) { 19 + return _ApplyAltsUpdate.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$ApplyAltsUpdate { 24 + String get photoUri => throw _privateConstructorUsedError; 25 + String get alt => throw _privateConstructorUsedError; 26 + 27 + /// Serializes this ApplyAltsUpdate to a JSON map. 28 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 29 + 30 + /// Create a copy of ApplyAltsUpdate 31 + /// with the given fields replaced by the non-null parameter values. 32 + @JsonKey(includeFromJson: false, includeToJson: false) 33 + $ApplyAltsUpdateCopyWith<ApplyAltsUpdate> get copyWith => 34 + throw _privateConstructorUsedError; 35 + } 36 + 37 + /// @nodoc 38 + abstract class $ApplyAltsUpdateCopyWith<$Res> { 39 + factory $ApplyAltsUpdateCopyWith( 40 + ApplyAltsUpdate value, 41 + $Res Function(ApplyAltsUpdate) then, 42 + ) = _$ApplyAltsUpdateCopyWithImpl<$Res, ApplyAltsUpdate>; 43 + @useResult 44 + $Res call({String photoUri, String alt}); 45 + } 46 + 47 + /// @nodoc 48 + class _$ApplyAltsUpdateCopyWithImpl<$Res, $Val extends ApplyAltsUpdate> 49 + implements $ApplyAltsUpdateCopyWith<$Res> { 50 + _$ApplyAltsUpdateCopyWithImpl(this._value, this._then); 51 + 52 + // ignore: unused_field 53 + final $Val _value; 54 + // ignore: unused_field 55 + final $Res Function($Val) _then; 56 + 57 + /// Create a copy of ApplyAltsUpdate 58 + /// with the given fields replaced by the non-null parameter values. 59 + @pragma('vm:prefer-inline') 60 + @override 61 + $Res call({Object? photoUri = null, Object? alt = null}) { 62 + return _then( 63 + _value.copyWith( 64 + photoUri: null == photoUri 65 + ? _value.photoUri 66 + : photoUri // ignore: cast_nullable_to_non_nullable 67 + as String, 68 + alt: null == alt 69 + ? _value.alt 70 + : alt // ignore: cast_nullable_to_non_nullable 71 + as String, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$ApplyAltsUpdateImplCopyWith<$Res> 80 + implements $ApplyAltsUpdateCopyWith<$Res> { 81 + factory _$$ApplyAltsUpdateImplCopyWith( 82 + _$ApplyAltsUpdateImpl value, 83 + $Res Function(_$ApplyAltsUpdateImpl) then, 84 + ) = __$$ApplyAltsUpdateImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({String photoUri, String alt}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$ApplyAltsUpdateImplCopyWithImpl<$Res> 92 + extends _$ApplyAltsUpdateCopyWithImpl<$Res, _$ApplyAltsUpdateImpl> 93 + implements _$$ApplyAltsUpdateImplCopyWith<$Res> { 94 + __$$ApplyAltsUpdateImplCopyWithImpl( 95 + _$ApplyAltsUpdateImpl _value, 96 + $Res Function(_$ApplyAltsUpdateImpl) _then, 97 + ) : super(_value, _then); 98 + 99 + /// Create a copy of ApplyAltsUpdate 100 + /// with the given fields replaced by the non-null parameter values. 101 + @pragma('vm:prefer-inline') 102 + @override 103 + $Res call({Object? photoUri = null, Object? alt = null}) { 104 + return _then( 105 + _$ApplyAltsUpdateImpl( 106 + photoUri: null == photoUri 107 + ? _value.photoUri 108 + : photoUri // ignore: cast_nullable_to_non_nullable 109 + as String, 110 + alt: null == alt 111 + ? _value.alt 112 + : alt // ignore: cast_nullable_to_non_nullable 113 + as String, 114 + ), 115 + ); 116 + } 117 + } 118 + 119 + /// @nodoc 120 + @JsonSerializable() 121 + class _$ApplyAltsUpdateImpl implements _ApplyAltsUpdate { 122 + const _$ApplyAltsUpdateImpl({required this.photoUri, required this.alt}); 123 + 124 + factory _$ApplyAltsUpdateImpl.fromJson(Map<String, dynamic> json) => 125 + _$$ApplyAltsUpdateImplFromJson(json); 126 + 127 + @override 128 + final String photoUri; 129 + @override 130 + final String alt; 131 + 132 + @override 133 + String toString() { 134 + return 'ApplyAltsUpdate(photoUri: $photoUri, alt: $alt)'; 135 + } 136 + 137 + @override 138 + bool operator ==(Object other) { 139 + return identical(this, other) || 140 + (other.runtimeType == runtimeType && 141 + other is _$ApplyAltsUpdateImpl && 142 + (identical(other.photoUri, photoUri) || 143 + other.photoUri == photoUri) && 144 + (identical(other.alt, alt) || other.alt == alt)); 145 + } 146 + 147 + @JsonKey(includeFromJson: false, includeToJson: false) 148 + @override 149 + int get hashCode => Object.hash(runtimeType, photoUri, alt); 150 + 151 + /// Create a copy of ApplyAltsUpdate 152 + /// with the given fields replaced by the non-null parameter values. 153 + @JsonKey(includeFromJson: false, includeToJson: false) 154 + @override 155 + @pragma('vm:prefer-inline') 156 + _$$ApplyAltsUpdateImplCopyWith<_$ApplyAltsUpdateImpl> get copyWith => 157 + __$$ApplyAltsUpdateImplCopyWithImpl<_$ApplyAltsUpdateImpl>( 158 + this, 159 + _$identity, 160 + ); 161 + 162 + @override 163 + Map<String, dynamic> toJson() { 164 + return _$$ApplyAltsUpdateImplToJson(this); 165 + } 166 + } 167 + 168 + abstract class _ApplyAltsUpdate implements ApplyAltsUpdate { 169 + const factory _ApplyAltsUpdate({ 170 + required final String photoUri, 171 + required final String alt, 172 + }) = _$ApplyAltsUpdateImpl; 173 + 174 + factory _ApplyAltsUpdate.fromJson(Map<String, dynamic> json) = 175 + _$ApplyAltsUpdateImpl.fromJson; 176 + 177 + @override 178 + String get photoUri; 179 + @override 180 + String get alt; 181 + 182 + /// Create a copy of ApplyAltsUpdate 183 + /// with the given fields replaced by the non-null parameter values. 184 + @override 185 + @JsonKey(includeFromJson: false, includeToJson: false) 186 + _$$ApplyAltsUpdateImplCopyWith<_$ApplyAltsUpdateImpl> get copyWith => 187 + throw _privateConstructorUsedError; 188 + }
+18
lib/models/procedures/apply_alts_update.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'apply_alts_update.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$ApplyAltsUpdateImpl _$$ApplyAltsUpdateImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$ApplyAltsUpdateImpl( 12 + photoUri: json['photoUri'] as String, 13 + alt: json['alt'] as String, 14 + ); 15 + 16 + Map<String, dynamic> _$$ApplyAltsUpdateImplToJson( 17 + _$ApplyAltsUpdateImpl instance, 18 + ) => <String, dynamic>{'photoUri': instance.photoUri, 'alt': instance.alt};
+24
lib/models/procedures/apply_sort_request.dart
···
··· 1 + import 'package:json_annotation/json_annotation.dart'; 2 + 3 + part 'apply_sort_request.g.dart'; 4 + 5 + @JsonSerializable() 6 + class ApplySortRequest { 7 + final List<ApplySortUpdate> writes; 8 + 9 + ApplySortRequest({required this.writes}); 10 + 11 + factory ApplySortRequest.fromJson(Map<String, dynamic> json) => _$ApplySortRequestFromJson(json); 12 + Map<String, dynamic> toJson() => _$ApplySortRequestToJson(this); 13 + } 14 + 15 + @JsonSerializable() 16 + class ApplySortUpdate { 17 + final String itemUri; 18 + final int position; 19 + 20 + ApplySortUpdate({required this.itemUri, required this.position}); 21 + 22 + factory ApplySortUpdate.fromJson(Map<String, dynamic> json) => _$ApplySortUpdateFromJson(json); 23 + Map<String, dynamic> toJson() => _$ApplySortUpdateToJson(this); 24 + }
+29
lib/models/procedures/apply_sort_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'apply_sort_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + ApplySortRequest _$ApplySortRequestFromJson(Map<String, dynamic> json) => 10 + ApplySortRequest( 11 + writes: (json['writes'] as List<dynamic>) 12 + .map((e) => ApplySortUpdate.fromJson(e as Map<String, dynamic>)) 13 + .toList(), 14 + ); 15 + 16 + Map<String, dynamic> _$ApplySortRequestToJson(ApplySortRequest instance) => 17 + <String, dynamic>{'writes': instance.writes}; 18 + 19 + ApplySortUpdate _$ApplySortUpdateFromJson(Map<String, dynamic> json) => 20 + ApplySortUpdate( 21 + itemUri: json['itemUri'] as String, 22 + position: (json['position'] as num).toInt(), 23 + ); 24 + 25 + Map<String, dynamic> _$ApplySortUpdateToJson(ApplySortUpdate instance) => 26 + <String, dynamic>{ 27 + 'itemUri': instance.itemUri, 28 + 'position': instance.position, 29 + };
+14
lib/models/procedures/apply_sort_response.dart
···
··· 1 + import 'package:json_annotation/json_annotation.dart'; 2 + 3 + part 'apply_sort_response.g.dart'; 4 + 5 + @JsonSerializable() 6 + class ApplySortResponse { 7 + final bool success; 8 + 9 + ApplySortResponse({required this.success}); 10 + 11 + factory ApplySortResponse.fromJson(Map<String, dynamic> json) => 12 + _$ApplySortResponseFromJson(json); 13 + Map<String, dynamic> toJson() => _$ApplySortResponseToJson(this); 14 + }
+13
lib/models/procedures/apply_sort_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'apply_sort_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + ApplySortResponse _$ApplySortResponseFromJson(Map<String, dynamic> json) => 10 + ApplySortResponse(success: json['success'] as bool); 11 + 12 + Map<String, dynamic> _$ApplySortResponseToJson(ApplySortResponse instance) => 13 + <String, dynamic>{'success': instance.success};
+19
lib/models/procedures/create_comment_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_comment_request.freezed.dart'; 4 + part 'create_comment_request.g.dart'; 5 + 6 + /// Request model for creating a comment. 7 + /// See lexicon: social.grain.comment.createComment 8 + @freezed 9 + class CreateCommentRequest with _$CreateCommentRequest { 10 + const factory CreateCommentRequest({ 11 + required String text, 12 + required String subject, 13 + String? focus, 14 + String? replyTo, 15 + }) = _CreateCommentRequest; 16 + 17 + factory CreateCommentRequest.fromJson(Map<String, dynamic> json) => 18 + _$CreateCommentRequestFromJson(json); 19 + }
+236
lib/models/procedures/create_comment_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_comment_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateCommentRequest _$CreateCommentRequestFromJson(Map<String, dynamic> json) { 19 + return _CreateCommentRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$CreateCommentRequest { 24 + String get text => throw _privateConstructorUsedError; 25 + String get subject => throw _privateConstructorUsedError; 26 + String? get focus => throw _privateConstructorUsedError; 27 + String? get replyTo => throw _privateConstructorUsedError; 28 + 29 + /// Serializes this CreateCommentRequest to a JSON map. 30 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 31 + 32 + /// Create a copy of CreateCommentRequest 33 + /// with the given fields replaced by the non-null parameter values. 34 + @JsonKey(includeFromJson: false, includeToJson: false) 35 + $CreateCommentRequestCopyWith<CreateCommentRequest> get copyWith => 36 + throw _privateConstructorUsedError; 37 + } 38 + 39 + /// @nodoc 40 + abstract class $CreateCommentRequestCopyWith<$Res> { 41 + factory $CreateCommentRequestCopyWith( 42 + CreateCommentRequest value, 43 + $Res Function(CreateCommentRequest) then, 44 + ) = _$CreateCommentRequestCopyWithImpl<$Res, CreateCommentRequest>; 45 + @useResult 46 + $Res call({String text, String subject, String? focus, String? replyTo}); 47 + } 48 + 49 + /// @nodoc 50 + class _$CreateCommentRequestCopyWithImpl< 51 + $Res, 52 + $Val extends CreateCommentRequest 53 + > 54 + implements $CreateCommentRequestCopyWith<$Res> { 55 + _$CreateCommentRequestCopyWithImpl(this._value, this._then); 56 + 57 + // ignore: unused_field 58 + final $Val _value; 59 + // ignore: unused_field 60 + final $Res Function($Val) _then; 61 + 62 + /// Create a copy of CreateCommentRequest 63 + /// with the given fields replaced by the non-null parameter values. 64 + @pragma('vm:prefer-inline') 65 + @override 66 + $Res call({ 67 + Object? text = null, 68 + Object? subject = null, 69 + Object? focus = freezed, 70 + Object? replyTo = freezed, 71 + }) { 72 + return _then( 73 + _value.copyWith( 74 + text: null == text 75 + ? _value.text 76 + : text // ignore: cast_nullable_to_non_nullable 77 + as String, 78 + subject: null == subject 79 + ? _value.subject 80 + : subject // ignore: cast_nullable_to_non_nullable 81 + as String, 82 + focus: freezed == focus 83 + ? _value.focus 84 + : focus // ignore: cast_nullable_to_non_nullable 85 + as String?, 86 + replyTo: freezed == replyTo 87 + ? _value.replyTo 88 + : replyTo // ignore: cast_nullable_to_non_nullable 89 + as String?, 90 + ) 91 + as $Val, 92 + ); 93 + } 94 + } 95 + 96 + /// @nodoc 97 + abstract class _$$CreateCommentRequestImplCopyWith<$Res> 98 + implements $CreateCommentRequestCopyWith<$Res> { 99 + factory _$$CreateCommentRequestImplCopyWith( 100 + _$CreateCommentRequestImpl value, 101 + $Res Function(_$CreateCommentRequestImpl) then, 102 + ) = __$$CreateCommentRequestImplCopyWithImpl<$Res>; 103 + @override 104 + @useResult 105 + $Res call({String text, String subject, String? focus, String? replyTo}); 106 + } 107 + 108 + /// @nodoc 109 + class __$$CreateCommentRequestImplCopyWithImpl<$Res> 110 + extends _$CreateCommentRequestCopyWithImpl<$Res, _$CreateCommentRequestImpl> 111 + implements _$$CreateCommentRequestImplCopyWith<$Res> { 112 + __$$CreateCommentRequestImplCopyWithImpl( 113 + _$CreateCommentRequestImpl _value, 114 + $Res Function(_$CreateCommentRequestImpl) _then, 115 + ) : super(_value, _then); 116 + 117 + /// Create a copy of CreateCommentRequest 118 + /// with the given fields replaced by the non-null parameter values. 119 + @pragma('vm:prefer-inline') 120 + @override 121 + $Res call({ 122 + Object? text = null, 123 + Object? subject = null, 124 + Object? focus = freezed, 125 + Object? replyTo = freezed, 126 + }) { 127 + return _then( 128 + _$CreateCommentRequestImpl( 129 + text: null == text 130 + ? _value.text 131 + : text // ignore: cast_nullable_to_non_nullable 132 + as String, 133 + subject: null == subject 134 + ? _value.subject 135 + : subject // ignore: cast_nullable_to_non_nullable 136 + as String, 137 + focus: freezed == focus 138 + ? _value.focus 139 + : focus // ignore: cast_nullable_to_non_nullable 140 + as String?, 141 + replyTo: freezed == replyTo 142 + ? _value.replyTo 143 + : replyTo // ignore: cast_nullable_to_non_nullable 144 + as String?, 145 + ), 146 + ); 147 + } 148 + } 149 + 150 + /// @nodoc 151 + @JsonSerializable() 152 + class _$CreateCommentRequestImpl implements _CreateCommentRequest { 153 + const _$CreateCommentRequestImpl({ 154 + required this.text, 155 + required this.subject, 156 + this.focus, 157 + this.replyTo, 158 + }); 159 + 160 + factory _$CreateCommentRequestImpl.fromJson(Map<String, dynamic> json) => 161 + _$$CreateCommentRequestImplFromJson(json); 162 + 163 + @override 164 + final String text; 165 + @override 166 + final String subject; 167 + @override 168 + final String? focus; 169 + @override 170 + final String? replyTo; 171 + 172 + @override 173 + String toString() { 174 + return 'CreateCommentRequest(text: $text, subject: $subject, focus: $focus, replyTo: $replyTo)'; 175 + } 176 + 177 + @override 178 + bool operator ==(Object other) { 179 + return identical(this, other) || 180 + (other.runtimeType == runtimeType && 181 + other is _$CreateCommentRequestImpl && 182 + (identical(other.text, text) || other.text == text) && 183 + (identical(other.subject, subject) || other.subject == subject) && 184 + (identical(other.focus, focus) || other.focus == focus) && 185 + (identical(other.replyTo, replyTo) || other.replyTo == replyTo)); 186 + } 187 + 188 + @JsonKey(includeFromJson: false, includeToJson: false) 189 + @override 190 + int get hashCode => Object.hash(runtimeType, text, subject, focus, replyTo); 191 + 192 + /// Create a copy of CreateCommentRequest 193 + /// with the given fields replaced by the non-null parameter values. 194 + @JsonKey(includeFromJson: false, includeToJson: false) 195 + @override 196 + @pragma('vm:prefer-inline') 197 + _$$CreateCommentRequestImplCopyWith<_$CreateCommentRequestImpl> 198 + get copyWith => 199 + __$$CreateCommentRequestImplCopyWithImpl<_$CreateCommentRequestImpl>( 200 + this, 201 + _$identity, 202 + ); 203 + 204 + @override 205 + Map<String, dynamic> toJson() { 206 + return _$$CreateCommentRequestImplToJson(this); 207 + } 208 + } 209 + 210 + abstract class _CreateCommentRequest implements CreateCommentRequest { 211 + const factory _CreateCommentRequest({ 212 + required final String text, 213 + required final String subject, 214 + final String? focus, 215 + final String? replyTo, 216 + }) = _$CreateCommentRequestImpl; 217 + 218 + factory _CreateCommentRequest.fromJson(Map<String, dynamic> json) = 219 + _$CreateCommentRequestImpl.fromJson; 220 + 221 + @override 222 + String get text; 223 + @override 224 + String get subject; 225 + @override 226 + String? get focus; 227 + @override 228 + String? get replyTo; 229 + 230 + /// Create a copy of CreateCommentRequest 231 + /// with the given fields replaced by the non-null parameter values. 232 + @override 233 + @JsonKey(includeFromJson: false, includeToJson: false) 234 + _$$CreateCommentRequestImplCopyWith<_$CreateCommentRequestImpl> 235 + get copyWith => throw _privateConstructorUsedError; 236 + }
+25
lib/models/procedures/create_comment_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_comment_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateCommentRequestImpl _$$CreateCommentRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateCommentRequestImpl( 12 + text: json['text'] as String, 13 + subject: json['subject'] as String, 14 + focus: json['focus'] as String?, 15 + replyTo: json['replyTo'] as String?, 16 + ); 17 + 18 + Map<String, dynamic> _$$CreateCommentRequestImplToJson( 19 + _$CreateCommentRequestImpl instance, 20 + ) => <String, dynamic>{ 21 + 'text': instance.text, 22 + 'subject': instance.subject, 23 + 'focus': instance.focus, 24 + 'replyTo': instance.replyTo, 25 + };
+14
lib/models/procedures/create_comment_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_comment_response.freezed.dart'; 4 + part 'create_comment_response.g.dart'; 5 + 6 + /// Response model for creating a comment. 7 + /// See lexicon: social.grain.comment.createComment 8 + @freezed 9 + class CreateCommentResponse with _$CreateCommentResponse { 10 + const factory CreateCommentResponse({required String commentUri}) = _CreateCommentResponse; 11 + 12 + factory CreateCommentResponse.fromJson(Map<String, dynamic> json) => 13 + _$CreateCommentResponseFromJson(json); 14 + }
+179
lib/models/procedures/create_comment_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_comment_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateCommentResponse _$CreateCommentResponseFromJson( 19 + Map<String, dynamic> json, 20 + ) { 21 + return _CreateCommentResponse.fromJson(json); 22 + } 23 + 24 + /// @nodoc 25 + mixin _$CreateCommentResponse { 26 + String get commentUri => throw _privateConstructorUsedError; 27 + 28 + /// Serializes this CreateCommentResponse to a JSON map. 29 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 30 + 31 + /// Create a copy of CreateCommentResponse 32 + /// with the given fields replaced by the non-null parameter values. 33 + @JsonKey(includeFromJson: false, includeToJson: false) 34 + $CreateCommentResponseCopyWith<CreateCommentResponse> get copyWith => 35 + throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $CreateCommentResponseCopyWith<$Res> { 40 + factory $CreateCommentResponseCopyWith( 41 + CreateCommentResponse value, 42 + $Res Function(CreateCommentResponse) then, 43 + ) = _$CreateCommentResponseCopyWithImpl<$Res, CreateCommentResponse>; 44 + @useResult 45 + $Res call({String commentUri}); 46 + } 47 + 48 + /// @nodoc 49 + class _$CreateCommentResponseCopyWithImpl< 50 + $Res, 51 + $Val extends CreateCommentResponse 52 + > 53 + implements $CreateCommentResponseCopyWith<$Res> { 54 + _$CreateCommentResponseCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of CreateCommentResponse 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({Object? commentUri = null}) { 66 + return _then( 67 + _value.copyWith( 68 + commentUri: null == commentUri 69 + ? _value.commentUri 70 + : commentUri // ignore: cast_nullable_to_non_nullable 71 + as String, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$CreateCommentResponseImplCopyWith<$Res> 80 + implements $CreateCommentResponseCopyWith<$Res> { 81 + factory _$$CreateCommentResponseImplCopyWith( 82 + _$CreateCommentResponseImpl value, 83 + $Res Function(_$CreateCommentResponseImpl) then, 84 + ) = __$$CreateCommentResponseImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({String commentUri}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$CreateCommentResponseImplCopyWithImpl<$Res> 92 + extends 93 + _$CreateCommentResponseCopyWithImpl<$Res, _$CreateCommentResponseImpl> 94 + implements _$$CreateCommentResponseImplCopyWith<$Res> { 95 + __$$CreateCommentResponseImplCopyWithImpl( 96 + _$CreateCommentResponseImpl _value, 97 + $Res Function(_$CreateCommentResponseImpl) _then, 98 + ) : super(_value, _then); 99 + 100 + /// Create a copy of CreateCommentResponse 101 + /// with the given fields replaced by the non-null parameter values. 102 + @pragma('vm:prefer-inline') 103 + @override 104 + $Res call({Object? commentUri = null}) { 105 + return _then( 106 + _$CreateCommentResponseImpl( 107 + commentUri: null == commentUri 108 + ? _value.commentUri 109 + : commentUri // ignore: cast_nullable_to_non_nullable 110 + as String, 111 + ), 112 + ); 113 + } 114 + } 115 + 116 + /// @nodoc 117 + @JsonSerializable() 118 + class _$CreateCommentResponseImpl implements _CreateCommentResponse { 119 + const _$CreateCommentResponseImpl({required this.commentUri}); 120 + 121 + factory _$CreateCommentResponseImpl.fromJson(Map<String, dynamic> json) => 122 + _$$CreateCommentResponseImplFromJson(json); 123 + 124 + @override 125 + final String commentUri; 126 + 127 + @override 128 + String toString() { 129 + return 'CreateCommentResponse(commentUri: $commentUri)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$CreateCommentResponseImpl && 137 + (identical(other.commentUri, commentUri) || 138 + other.commentUri == commentUri)); 139 + } 140 + 141 + @JsonKey(includeFromJson: false, includeToJson: false) 142 + @override 143 + int get hashCode => Object.hash(runtimeType, commentUri); 144 + 145 + /// Create a copy of CreateCommentResponse 146 + /// with the given fields replaced by the non-null parameter values. 147 + @JsonKey(includeFromJson: false, includeToJson: false) 148 + @override 149 + @pragma('vm:prefer-inline') 150 + _$$CreateCommentResponseImplCopyWith<_$CreateCommentResponseImpl> 151 + get copyWith => 152 + __$$CreateCommentResponseImplCopyWithImpl<_$CreateCommentResponseImpl>( 153 + this, 154 + _$identity, 155 + ); 156 + 157 + @override 158 + Map<String, dynamic> toJson() { 159 + return _$$CreateCommentResponseImplToJson(this); 160 + } 161 + } 162 + 163 + abstract class _CreateCommentResponse implements CreateCommentResponse { 164 + const factory _CreateCommentResponse({required final String commentUri}) = 165 + _$CreateCommentResponseImpl; 166 + 167 + factory _CreateCommentResponse.fromJson(Map<String, dynamic> json) = 168 + _$CreateCommentResponseImpl.fromJson; 169 + 170 + @override 171 + String get commentUri; 172 + 173 + /// Create a copy of CreateCommentResponse 174 + /// with the given fields replaced by the non-null parameter values. 175 + @override 176 + @JsonKey(includeFromJson: false, includeToJson: false) 177 + _$$CreateCommentResponseImplCopyWith<_$CreateCommentResponseImpl> 178 + get copyWith => throw _privateConstructorUsedError; 179 + }
+15
lib/models/procedures/create_comment_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_comment_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateCommentResponseImpl _$$CreateCommentResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateCommentResponseImpl(commentUri: json['commentUri'] as String); 12 + 13 + Map<String, dynamic> _$$CreateCommentResponseImplToJson( 14 + _$CreateCommentResponseImpl instance, 15 + ) => <String, dynamic>{'commentUri': instance.commentUri};
+25
lib/models/procedures/create_exif_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_exif_request.freezed.dart'; 4 + part 'create_exif_request.g.dart'; 5 + 6 + /// Request model for creating a new Exif record for a photo. 7 + @freezed 8 + class CreateExifRequest with _$CreateExifRequest { 9 + const factory CreateExifRequest({ 10 + required String photo, 11 + String? dateTimeOriginal, 12 + int? exposureTime, 13 + int? fNumber, 14 + String? flash, 15 + int? focalLengthIn35mmFormat, 16 + int? iSO, 17 + String? lensMake, 18 + String? lensModel, 19 + String? make, 20 + String? model, 21 + }) = _CreateExifRequest; 22 + 23 + factory CreateExifRequest.fromJson(Map<String, dynamic> json) => 24 + _$CreateExifRequestFromJson(json); 25 + }
+403
lib/models/procedures/create_exif_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_exif_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateExifRequest _$CreateExifRequestFromJson(Map<String, dynamic> json) { 19 + return _CreateExifRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$CreateExifRequest { 24 + String get photo => throw _privateConstructorUsedError; 25 + String? get dateTimeOriginal => throw _privateConstructorUsedError; 26 + int? get exposureTime => throw _privateConstructorUsedError; 27 + int? get fNumber => throw _privateConstructorUsedError; 28 + String? get flash => throw _privateConstructorUsedError; 29 + int? get focalLengthIn35mmFormat => throw _privateConstructorUsedError; 30 + int? get iSO => throw _privateConstructorUsedError; 31 + String? get lensMake => throw _privateConstructorUsedError; 32 + String? get lensModel => throw _privateConstructorUsedError; 33 + String? get make => throw _privateConstructorUsedError; 34 + String? get model => throw _privateConstructorUsedError; 35 + 36 + /// Serializes this CreateExifRequest to a JSON map. 37 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 38 + 39 + /// Create a copy of CreateExifRequest 40 + /// with the given fields replaced by the non-null parameter values. 41 + @JsonKey(includeFromJson: false, includeToJson: false) 42 + $CreateExifRequestCopyWith<CreateExifRequest> get copyWith => 43 + throw _privateConstructorUsedError; 44 + } 45 + 46 + /// @nodoc 47 + abstract class $CreateExifRequestCopyWith<$Res> { 48 + factory $CreateExifRequestCopyWith( 49 + CreateExifRequest value, 50 + $Res Function(CreateExifRequest) then, 51 + ) = _$CreateExifRequestCopyWithImpl<$Res, CreateExifRequest>; 52 + @useResult 53 + $Res call({ 54 + String photo, 55 + String? dateTimeOriginal, 56 + int? exposureTime, 57 + int? fNumber, 58 + String? flash, 59 + int? focalLengthIn35mmFormat, 60 + int? iSO, 61 + String? lensMake, 62 + String? lensModel, 63 + String? make, 64 + String? model, 65 + }); 66 + } 67 + 68 + /// @nodoc 69 + class _$CreateExifRequestCopyWithImpl<$Res, $Val extends CreateExifRequest> 70 + implements $CreateExifRequestCopyWith<$Res> { 71 + _$CreateExifRequestCopyWithImpl(this._value, this._then); 72 + 73 + // ignore: unused_field 74 + final $Val _value; 75 + // ignore: unused_field 76 + final $Res Function($Val) _then; 77 + 78 + /// Create a copy of CreateExifRequest 79 + /// with the given fields replaced by the non-null parameter values. 80 + @pragma('vm:prefer-inline') 81 + @override 82 + $Res call({ 83 + Object? photo = null, 84 + Object? dateTimeOriginal = freezed, 85 + Object? exposureTime = freezed, 86 + Object? fNumber = freezed, 87 + Object? flash = freezed, 88 + Object? focalLengthIn35mmFormat = freezed, 89 + Object? iSO = freezed, 90 + Object? lensMake = freezed, 91 + Object? lensModel = freezed, 92 + Object? make = freezed, 93 + Object? model = freezed, 94 + }) { 95 + return _then( 96 + _value.copyWith( 97 + photo: null == photo 98 + ? _value.photo 99 + : photo // ignore: cast_nullable_to_non_nullable 100 + as String, 101 + dateTimeOriginal: freezed == dateTimeOriginal 102 + ? _value.dateTimeOriginal 103 + : dateTimeOriginal // ignore: cast_nullable_to_non_nullable 104 + as String?, 105 + exposureTime: freezed == exposureTime 106 + ? _value.exposureTime 107 + : exposureTime // ignore: cast_nullable_to_non_nullable 108 + as int?, 109 + fNumber: freezed == fNumber 110 + ? _value.fNumber 111 + : fNumber // ignore: cast_nullable_to_non_nullable 112 + as int?, 113 + flash: freezed == flash 114 + ? _value.flash 115 + : flash // ignore: cast_nullable_to_non_nullable 116 + as String?, 117 + focalLengthIn35mmFormat: freezed == focalLengthIn35mmFormat 118 + ? _value.focalLengthIn35mmFormat 119 + : focalLengthIn35mmFormat // ignore: cast_nullable_to_non_nullable 120 + as int?, 121 + iSO: freezed == iSO 122 + ? _value.iSO 123 + : iSO // ignore: cast_nullable_to_non_nullable 124 + as int?, 125 + lensMake: freezed == lensMake 126 + ? _value.lensMake 127 + : lensMake // ignore: cast_nullable_to_non_nullable 128 + as String?, 129 + lensModel: freezed == lensModel 130 + ? _value.lensModel 131 + : lensModel // ignore: cast_nullable_to_non_nullable 132 + as String?, 133 + make: freezed == make 134 + ? _value.make 135 + : make // ignore: cast_nullable_to_non_nullable 136 + as String?, 137 + model: freezed == model 138 + ? _value.model 139 + : model // ignore: cast_nullable_to_non_nullable 140 + as String?, 141 + ) 142 + as $Val, 143 + ); 144 + } 145 + } 146 + 147 + /// @nodoc 148 + abstract class _$$CreateExifRequestImplCopyWith<$Res> 149 + implements $CreateExifRequestCopyWith<$Res> { 150 + factory _$$CreateExifRequestImplCopyWith( 151 + _$CreateExifRequestImpl value, 152 + $Res Function(_$CreateExifRequestImpl) then, 153 + ) = __$$CreateExifRequestImplCopyWithImpl<$Res>; 154 + @override 155 + @useResult 156 + $Res call({ 157 + String photo, 158 + String? dateTimeOriginal, 159 + int? exposureTime, 160 + int? fNumber, 161 + String? flash, 162 + int? focalLengthIn35mmFormat, 163 + int? iSO, 164 + String? lensMake, 165 + String? lensModel, 166 + String? make, 167 + String? model, 168 + }); 169 + } 170 + 171 + /// @nodoc 172 + class __$$CreateExifRequestImplCopyWithImpl<$Res> 173 + extends _$CreateExifRequestCopyWithImpl<$Res, _$CreateExifRequestImpl> 174 + implements _$$CreateExifRequestImplCopyWith<$Res> { 175 + __$$CreateExifRequestImplCopyWithImpl( 176 + _$CreateExifRequestImpl _value, 177 + $Res Function(_$CreateExifRequestImpl) _then, 178 + ) : super(_value, _then); 179 + 180 + /// Create a copy of CreateExifRequest 181 + /// with the given fields replaced by the non-null parameter values. 182 + @pragma('vm:prefer-inline') 183 + @override 184 + $Res call({ 185 + Object? photo = null, 186 + Object? dateTimeOriginal = freezed, 187 + Object? exposureTime = freezed, 188 + Object? fNumber = freezed, 189 + Object? flash = freezed, 190 + Object? focalLengthIn35mmFormat = freezed, 191 + Object? iSO = freezed, 192 + Object? lensMake = freezed, 193 + Object? lensModel = freezed, 194 + Object? make = freezed, 195 + Object? model = freezed, 196 + }) { 197 + return _then( 198 + _$CreateExifRequestImpl( 199 + photo: null == photo 200 + ? _value.photo 201 + : photo // ignore: cast_nullable_to_non_nullable 202 + as String, 203 + dateTimeOriginal: freezed == dateTimeOriginal 204 + ? _value.dateTimeOriginal 205 + : dateTimeOriginal // ignore: cast_nullable_to_non_nullable 206 + as String?, 207 + exposureTime: freezed == exposureTime 208 + ? _value.exposureTime 209 + : exposureTime // ignore: cast_nullable_to_non_nullable 210 + as int?, 211 + fNumber: freezed == fNumber 212 + ? _value.fNumber 213 + : fNumber // ignore: cast_nullable_to_non_nullable 214 + as int?, 215 + flash: freezed == flash 216 + ? _value.flash 217 + : flash // ignore: cast_nullable_to_non_nullable 218 + as String?, 219 + focalLengthIn35mmFormat: freezed == focalLengthIn35mmFormat 220 + ? _value.focalLengthIn35mmFormat 221 + : focalLengthIn35mmFormat // ignore: cast_nullable_to_non_nullable 222 + as int?, 223 + iSO: freezed == iSO 224 + ? _value.iSO 225 + : iSO // ignore: cast_nullable_to_non_nullable 226 + as int?, 227 + lensMake: freezed == lensMake 228 + ? _value.lensMake 229 + : lensMake // ignore: cast_nullable_to_non_nullable 230 + as String?, 231 + lensModel: freezed == lensModel 232 + ? _value.lensModel 233 + : lensModel // ignore: cast_nullable_to_non_nullable 234 + as String?, 235 + make: freezed == make 236 + ? _value.make 237 + : make // ignore: cast_nullable_to_non_nullable 238 + as String?, 239 + model: freezed == model 240 + ? _value.model 241 + : model // ignore: cast_nullable_to_non_nullable 242 + as String?, 243 + ), 244 + ); 245 + } 246 + } 247 + 248 + /// @nodoc 249 + @JsonSerializable() 250 + class _$CreateExifRequestImpl implements _CreateExifRequest { 251 + const _$CreateExifRequestImpl({ 252 + required this.photo, 253 + this.dateTimeOriginal, 254 + this.exposureTime, 255 + this.fNumber, 256 + this.flash, 257 + this.focalLengthIn35mmFormat, 258 + this.iSO, 259 + this.lensMake, 260 + this.lensModel, 261 + this.make, 262 + this.model, 263 + }); 264 + 265 + factory _$CreateExifRequestImpl.fromJson(Map<String, dynamic> json) => 266 + _$$CreateExifRequestImplFromJson(json); 267 + 268 + @override 269 + final String photo; 270 + @override 271 + final String? dateTimeOriginal; 272 + @override 273 + final int? exposureTime; 274 + @override 275 + final int? fNumber; 276 + @override 277 + final String? flash; 278 + @override 279 + final int? focalLengthIn35mmFormat; 280 + @override 281 + final int? iSO; 282 + @override 283 + final String? lensMake; 284 + @override 285 + final String? lensModel; 286 + @override 287 + final String? make; 288 + @override 289 + final String? model; 290 + 291 + @override 292 + String toString() { 293 + return 'CreateExifRequest(photo: $photo, dateTimeOriginal: $dateTimeOriginal, exposureTime: $exposureTime, fNumber: $fNumber, flash: $flash, focalLengthIn35mmFormat: $focalLengthIn35mmFormat, iSO: $iSO, lensMake: $lensMake, lensModel: $lensModel, make: $make, model: $model)'; 294 + } 295 + 296 + @override 297 + bool operator ==(Object other) { 298 + return identical(this, other) || 299 + (other.runtimeType == runtimeType && 300 + other is _$CreateExifRequestImpl && 301 + (identical(other.photo, photo) || other.photo == photo) && 302 + (identical(other.dateTimeOriginal, dateTimeOriginal) || 303 + other.dateTimeOriginal == dateTimeOriginal) && 304 + (identical(other.exposureTime, exposureTime) || 305 + other.exposureTime == exposureTime) && 306 + (identical(other.fNumber, fNumber) || other.fNumber == fNumber) && 307 + (identical(other.flash, flash) || other.flash == flash) && 308 + (identical( 309 + other.focalLengthIn35mmFormat, 310 + focalLengthIn35mmFormat, 311 + ) || 312 + other.focalLengthIn35mmFormat == focalLengthIn35mmFormat) && 313 + (identical(other.iSO, iSO) || other.iSO == iSO) && 314 + (identical(other.lensMake, lensMake) || 315 + other.lensMake == lensMake) && 316 + (identical(other.lensModel, lensModel) || 317 + other.lensModel == lensModel) && 318 + (identical(other.make, make) || other.make == make) && 319 + (identical(other.model, model) || other.model == model)); 320 + } 321 + 322 + @JsonKey(includeFromJson: false, includeToJson: false) 323 + @override 324 + int get hashCode => Object.hash( 325 + runtimeType, 326 + photo, 327 + dateTimeOriginal, 328 + exposureTime, 329 + fNumber, 330 + flash, 331 + focalLengthIn35mmFormat, 332 + iSO, 333 + lensMake, 334 + lensModel, 335 + make, 336 + model, 337 + ); 338 + 339 + /// Create a copy of CreateExifRequest 340 + /// with the given fields replaced by the non-null parameter values. 341 + @JsonKey(includeFromJson: false, includeToJson: false) 342 + @override 343 + @pragma('vm:prefer-inline') 344 + _$$CreateExifRequestImplCopyWith<_$CreateExifRequestImpl> get copyWith => 345 + __$$CreateExifRequestImplCopyWithImpl<_$CreateExifRequestImpl>( 346 + this, 347 + _$identity, 348 + ); 349 + 350 + @override 351 + Map<String, dynamic> toJson() { 352 + return _$$CreateExifRequestImplToJson(this); 353 + } 354 + } 355 + 356 + abstract class _CreateExifRequest implements CreateExifRequest { 357 + const factory _CreateExifRequest({ 358 + required final String photo, 359 + final String? dateTimeOriginal, 360 + final int? exposureTime, 361 + final int? fNumber, 362 + final String? flash, 363 + final int? focalLengthIn35mmFormat, 364 + final int? iSO, 365 + final String? lensMake, 366 + final String? lensModel, 367 + final String? make, 368 + final String? model, 369 + }) = _$CreateExifRequestImpl; 370 + 371 + factory _CreateExifRequest.fromJson(Map<String, dynamic> json) = 372 + _$CreateExifRequestImpl.fromJson; 373 + 374 + @override 375 + String get photo; 376 + @override 377 + String? get dateTimeOriginal; 378 + @override 379 + int? get exposureTime; 380 + @override 381 + int? get fNumber; 382 + @override 383 + String? get flash; 384 + @override 385 + int? get focalLengthIn35mmFormat; 386 + @override 387 + int? get iSO; 388 + @override 389 + String? get lensMake; 390 + @override 391 + String? get lensModel; 392 + @override 393 + String? get make; 394 + @override 395 + String? get model; 396 + 397 + /// Create a copy of CreateExifRequest 398 + /// with the given fields replaced by the non-null parameter values. 399 + @override 400 + @JsonKey(includeFromJson: false, includeToJson: false) 401 + _$$CreateExifRequestImplCopyWith<_$CreateExifRequestImpl> get copyWith => 402 + throw _privateConstructorUsedError; 403 + }
+39
lib/models/procedures/create_exif_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_exif_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateExifRequestImpl _$$CreateExifRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateExifRequestImpl( 12 + photo: json['photo'] as String, 13 + dateTimeOriginal: json['dateTimeOriginal'] as String?, 14 + exposureTime: (json['exposureTime'] as num?)?.toInt(), 15 + fNumber: (json['fNumber'] as num?)?.toInt(), 16 + flash: json['flash'] as String?, 17 + focalLengthIn35mmFormat: (json['focalLengthIn35mmFormat'] as num?)?.toInt(), 18 + iSO: (json['iSO'] as num?)?.toInt(), 19 + lensMake: json['lensMake'] as String?, 20 + lensModel: json['lensModel'] as String?, 21 + make: json['make'] as String?, 22 + model: json['model'] as String?, 23 + ); 24 + 25 + Map<String, dynamic> _$$CreateExifRequestImplToJson( 26 + _$CreateExifRequestImpl instance, 27 + ) => <String, dynamic>{ 28 + 'photo': instance.photo, 29 + 'dateTimeOriginal': instance.dateTimeOriginal, 30 + 'exposureTime': instance.exposureTime, 31 + 'fNumber': instance.fNumber, 32 + 'flash': instance.flash, 33 + 'focalLengthIn35mmFormat': instance.focalLengthIn35mmFormat, 34 + 'iSO': instance.iSO, 35 + 'lensMake': instance.lensMake, 36 + 'lensModel': instance.lensModel, 37 + 'make': instance.make, 38 + 'model': instance.model, 39 + };
+15
lib/models/procedures/create_exif_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_exif_response.freezed.dart'; 4 + part 'create_exif_response.g.dart'; 5 + 6 + /// Response model for creating a new Exif record for a photo. 7 + /// 8 + /// [exifUri] - The URI of the created Exif record. 9 + @freezed 10 + class CreateExifResponse with _$CreateExifResponse { 11 + const factory CreateExifResponse({required String exifUri}) = _CreateExifResponse; 12 + 13 + factory CreateExifResponse.fromJson(Map<String, dynamic> json) => 14 + _$CreateExifResponseFromJson(json); 15 + }
+171
lib/models/procedures/create_exif_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_exif_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateExifResponse _$CreateExifResponseFromJson(Map<String, dynamic> json) { 19 + return _CreateExifResponse.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$CreateExifResponse { 24 + String get exifUri => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this CreateExifResponse to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of CreateExifResponse 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $CreateExifResponseCopyWith<CreateExifResponse> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $CreateExifResponseCopyWith<$Res> { 38 + factory $CreateExifResponseCopyWith( 39 + CreateExifResponse value, 40 + $Res Function(CreateExifResponse) then, 41 + ) = _$CreateExifResponseCopyWithImpl<$Res, CreateExifResponse>; 42 + @useResult 43 + $Res call({String exifUri}); 44 + } 45 + 46 + /// @nodoc 47 + class _$CreateExifResponseCopyWithImpl<$Res, $Val extends CreateExifResponse> 48 + implements $CreateExifResponseCopyWith<$Res> { 49 + _$CreateExifResponseCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of CreateExifResponse 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? exifUri = null}) { 61 + return _then( 62 + _value.copyWith( 63 + exifUri: null == exifUri 64 + ? _value.exifUri 65 + : exifUri // ignore: cast_nullable_to_non_nullable 66 + as String, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$CreateExifResponseImplCopyWith<$Res> 75 + implements $CreateExifResponseCopyWith<$Res> { 76 + factory _$$CreateExifResponseImplCopyWith( 77 + _$CreateExifResponseImpl value, 78 + $Res Function(_$CreateExifResponseImpl) then, 79 + ) = __$$CreateExifResponseImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({String exifUri}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$CreateExifResponseImplCopyWithImpl<$Res> 87 + extends _$CreateExifResponseCopyWithImpl<$Res, _$CreateExifResponseImpl> 88 + implements _$$CreateExifResponseImplCopyWith<$Res> { 89 + __$$CreateExifResponseImplCopyWithImpl( 90 + _$CreateExifResponseImpl _value, 91 + $Res Function(_$CreateExifResponseImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of CreateExifResponse 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? exifUri = null}) { 99 + return _then( 100 + _$CreateExifResponseImpl( 101 + exifUri: null == exifUri 102 + ? _value.exifUri 103 + : exifUri // ignore: cast_nullable_to_non_nullable 104 + as String, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$CreateExifResponseImpl implements _CreateExifResponse { 113 + const _$CreateExifResponseImpl({required this.exifUri}); 114 + 115 + factory _$CreateExifResponseImpl.fromJson(Map<String, dynamic> json) => 116 + _$$CreateExifResponseImplFromJson(json); 117 + 118 + @override 119 + final String exifUri; 120 + 121 + @override 122 + String toString() { 123 + return 'CreateExifResponse(exifUri: $exifUri)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$CreateExifResponseImpl && 131 + (identical(other.exifUri, exifUri) || other.exifUri == exifUri)); 132 + } 133 + 134 + @JsonKey(includeFromJson: false, includeToJson: false) 135 + @override 136 + int get hashCode => Object.hash(runtimeType, exifUri); 137 + 138 + /// Create a copy of CreateExifResponse 139 + /// with the given fields replaced by the non-null parameter values. 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + @pragma('vm:prefer-inline') 143 + _$$CreateExifResponseImplCopyWith<_$CreateExifResponseImpl> get copyWith => 144 + __$$CreateExifResponseImplCopyWithImpl<_$CreateExifResponseImpl>( 145 + this, 146 + _$identity, 147 + ); 148 + 149 + @override 150 + Map<String, dynamic> toJson() { 151 + return _$$CreateExifResponseImplToJson(this); 152 + } 153 + } 154 + 155 + abstract class _CreateExifResponse implements CreateExifResponse { 156 + const factory _CreateExifResponse({required final String exifUri}) = 157 + _$CreateExifResponseImpl; 158 + 159 + factory _CreateExifResponse.fromJson(Map<String, dynamic> json) = 160 + _$CreateExifResponseImpl.fromJson; 161 + 162 + @override 163 + String get exifUri; 164 + 165 + /// Create a copy of CreateExifResponse 166 + /// with the given fields replaced by the non-null parameter values. 167 + @override 168 + @JsonKey(includeFromJson: false, includeToJson: false) 169 + _$$CreateExifResponseImplCopyWith<_$CreateExifResponseImpl> get copyWith => 170 + throw _privateConstructorUsedError; 171 + }
+15
lib/models/procedures/create_exif_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_exif_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateExifResponseImpl _$$CreateExifResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateExifResponseImpl(exifUri: json['exifUri'] as String); 12 + 13 + Map<String, dynamic> _$$CreateExifResponseImplToJson( 14 + _$CreateExifResponseImpl instance, 15 + ) => <String, dynamic>{'exifUri': instance.exifUri};
+14
lib/models/procedures/create_favorite_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_favorite_request.freezed.dart'; 4 + part 'create_favorite_request.g.dart'; 5 + 6 + /// Request model for creating a favorite. 7 + /// See lexicon: social.grain.favorite.createFavorite 8 + @freezed 9 + class CreateFavoriteRequest with _$CreateFavoriteRequest { 10 + const factory CreateFavoriteRequest({required String subject}) = _CreateFavoriteRequest; 11 + 12 + factory CreateFavoriteRequest.fromJson(Map<String, dynamic> json) => 13 + _$CreateFavoriteRequestFromJson(json); 14 + }
+178
lib/models/procedures/create_favorite_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_favorite_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateFavoriteRequest _$CreateFavoriteRequestFromJson( 19 + Map<String, dynamic> json, 20 + ) { 21 + return _CreateFavoriteRequest.fromJson(json); 22 + } 23 + 24 + /// @nodoc 25 + mixin _$CreateFavoriteRequest { 26 + String get subject => throw _privateConstructorUsedError; 27 + 28 + /// Serializes this CreateFavoriteRequest to a JSON map. 29 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 30 + 31 + /// Create a copy of CreateFavoriteRequest 32 + /// with the given fields replaced by the non-null parameter values. 33 + @JsonKey(includeFromJson: false, includeToJson: false) 34 + $CreateFavoriteRequestCopyWith<CreateFavoriteRequest> get copyWith => 35 + throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $CreateFavoriteRequestCopyWith<$Res> { 40 + factory $CreateFavoriteRequestCopyWith( 41 + CreateFavoriteRequest value, 42 + $Res Function(CreateFavoriteRequest) then, 43 + ) = _$CreateFavoriteRequestCopyWithImpl<$Res, CreateFavoriteRequest>; 44 + @useResult 45 + $Res call({String subject}); 46 + } 47 + 48 + /// @nodoc 49 + class _$CreateFavoriteRequestCopyWithImpl< 50 + $Res, 51 + $Val extends CreateFavoriteRequest 52 + > 53 + implements $CreateFavoriteRequestCopyWith<$Res> { 54 + _$CreateFavoriteRequestCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of CreateFavoriteRequest 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({Object? subject = null}) { 66 + return _then( 67 + _value.copyWith( 68 + subject: null == subject 69 + ? _value.subject 70 + : subject // ignore: cast_nullable_to_non_nullable 71 + as String, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$CreateFavoriteRequestImplCopyWith<$Res> 80 + implements $CreateFavoriteRequestCopyWith<$Res> { 81 + factory _$$CreateFavoriteRequestImplCopyWith( 82 + _$CreateFavoriteRequestImpl value, 83 + $Res Function(_$CreateFavoriteRequestImpl) then, 84 + ) = __$$CreateFavoriteRequestImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({String subject}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$CreateFavoriteRequestImplCopyWithImpl<$Res> 92 + extends 93 + _$CreateFavoriteRequestCopyWithImpl<$Res, _$CreateFavoriteRequestImpl> 94 + implements _$$CreateFavoriteRequestImplCopyWith<$Res> { 95 + __$$CreateFavoriteRequestImplCopyWithImpl( 96 + _$CreateFavoriteRequestImpl _value, 97 + $Res Function(_$CreateFavoriteRequestImpl) _then, 98 + ) : super(_value, _then); 99 + 100 + /// Create a copy of CreateFavoriteRequest 101 + /// with the given fields replaced by the non-null parameter values. 102 + @pragma('vm:prefer-inline') 103 + @override 104 + $Res call({Object? subject = null}) { 105 + return _then( 106 + _$CreateFavoriteRequestImpl( 107 + subject: null == subject 108 + ? _value.subject 109 + : subject // ignore: cast_nullable_to_non_nullable 110 + as String, 111 + ), 112 + ); 113 + } 114 + } 115 + 116 + /// @nodoc 117 + @JsonSerializable() 118 + class _$CreateFavoriteRequestImpl implements _CreateFavoriteRequest { 119 + const _$CreateFavoriteRequestImpl({required this.subject}); 120 + 121 + factory _$CreateFavoriteRequestImpl.fromJson(Map<String, dynamic> json) => 122 + _$$CreateFavoriteRequestImplFromJson(json); 123 + 124 + @override 125 + final String subject; 126 + 127 + @override 128 + String toString() { 129 + return 'CreateFavoriteRequest(subject: $subject)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$CreateFavoriteRequestImpl && 137 + (identical(other.subject, subject) || other.subject == subject)); 138 + } 139 + 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + int get hashCode => Object.hash(runtimeType, subject); 143 + 144 + /// Create a copy of CreateFavoriteRequest 145 + /// with the given fields replaced by the non-null parameter values. 146 + @JsonKey(includeFromJson: false, includeToJson: false) 147 + @override 148 + @pragma('vm:prefer-inline') 149 + _$$CreateFavoriteRequestImplCopyWith<_$CreateFavoriteRequestImpl> 150 + get copyWith => 151 + __$$CreateFavoriteRequestImplCopyWithImpl<_$CreateFavoriteRequestImpl>( 152 + this, 153 + _$identity, 154 + ); 155 + 156 + @override 157 + Map<String, dynamic> toJson() { 158 + return _$$CreateFavoriteRequestImplToJson(this); 159 + } 160 + } 161 + 162 + abstract class _CreateFavoriteRequest implements CreateFavoriteRequest { 163 + const factory _CreateFavoriteRequest({required final String subject}) = 164 + _$CreateFavoriteRequestImpl; 165 + 166 + factory _CreateFavoriteRequest.fromJson(Map<String, dynamic> json) = 167 + _$CreateFavoriteRequestImpl.fromJson; 168 + 169 + @override 170 + String get subject; 171 + 172 + /// Create a copy of CreateFavoriteRequest 173 + /// with the given fields replaced by the non-null parameter values. 174 + @override 175 + @JsonKey(includeFromJson: false, includeToJson: false) 176 + _$$CreateFavoriteRequestImplCopyWith<_$CreateFavoriteRequestImpl> 177 + get copyWith => throw _privateConstructorUsedError; 178 + }
+15
lib/models/procedures/create_favorite_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_favorite_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateFavoriteRequestImpl _$$CreateFavoriteRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateFavoriteRequestImpl(subject: json['subject'] as String); 12 + 13 + Map<String, dynamic> _$$CreateFavoriteRequestImplToJson( 14 + _$CreateFavoriteRequestImpl instance, 15 + ) => <String, dynamic>{'subject': instance.subject};
+14
lib/models/procedures/create_favorite_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_favorite_response.freezed.dart'; 4 + part 'create_favorite_response.g.dart'; 5 + 6 + /// Response model for creating a favorite. 7 + /// See lexicon: social.grain.favorite.createFavorite 8 + @freezed 9 + class CreateFavoriteResponse with _$CreateFavoriteResponse { 10 + const factory CreateFavoriteResponse({required String favoriteUri}) = _CreateFavoriteResponse; 11 + 12 + factory CreateFavoriteResponse.fromJson(Map<String, dynamic> json) => 13 + _$CreateFavoriteResponseFromJson(json); 14 + }
+179
lib/models/procedures/create_favorite_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_favorite_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateFavoriteResponse _$CreateFavoriteResponseFromJson( 19 + Map<String, dynamic> json, 20 + ) { 21 + return _CreateFavoriteResponse.fromJson(json); 22 + } 23 + 24 + /// @nodoc 25 + mixin _$CreateFavoriteResponse { 26 + String get favoriteUri => throw _privateConstructorUsedError; 27 + 28 + /// Serializes this CreateFavoriteResponse to a JSON map. 29 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 30 + 31 + /// Create a copy of CreateFavoriteResponse 32 + /// with the given fields replaced by the non-null parameter values. 33 + @JsonKey(includeFromJson: false, includeToJson: false) 34 + $CreateFavoriteResponseCopyWith<CreateFavoriteResponse> get copyWith => 35 + throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $CreateFavoriteResponseCopyWith<$Res> { 40 + factory $CreateFavoriteResponseCopyWith( 41 + CreateFavoriteResponse value, 42 + $Res Function(CreateFavoriteResponse) then, 43 + ) = _$CreateFavoriteResponseCopyWithImpl<$Res, CreateFavoriteResponse>; 44 + @useResult 45 + $Res call({String favoriteUri}); 46 + } 47 + 48 + /// @nodoc 49 + class _$CreateFavoriteResponseCopyWithImpl< 50 + $Res, 51 + $Val extends CreateFavoriteResponse 52 + > 53 + implements $CreateFavoriteResponseCopyWith<$Res> { 54 + _$CreateFavoriteResponseCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of CreateFavoriteResponse 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({Object? favoriteUri = null}) { 66 + return _then( 67 + _value.copyWith( 68 + favoriteUri: null == favoriteUri 69 + ? _value.favoriteUri 70 + : favoriteUri // ignore: cast_nullable_to_non_nullable 71 + as String, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$CreateFavoriteResponseImplCopyWith<$Res> 80 + implements $CreateFavoriteResponseCopyWith<$Res> { 81 + factory _$$CreateFavoriteResponseImplCopyWith( 82 + _$CreateFavoriteResponseImpl value, 83 + $Res Function(_$CreateFavoriteResponseImpl) then, 84 + ) = __$$CreateFavoriteResponseImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({String favoriteUri}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$CreateFavoriteResponseImplCopyWithImpl<$Res> 92 + extends 93 + _$CreateFavoriteResponseCopyWithImpl<$Res, _$CreateFavoriteResponseImpl> 94 + implements _$$CreateFavoriteResponseImplCopyWith<$Res> { 95 + __$$CreateFavoriteResponseImplCopyWithImpl( 96 + _$CreateFavoriteResponseImpl _value, 97 + $Res Function(_$CreateFavoriteResponseImpl) _then, 98 + ) : super(_value, _then); 99 + 100 + /// Create a copy of CreateFavoriteResponse 101 + /// with the given fields replaced by the non-null parameter values. 102 + @pragma('vm:prefer-inline') 103 + @override 104 + $Res call({Object? favoriteUri = null}) { 105 + return _then( 106 + _$CreateFavoriteResponseImpl( 107 + favoriteUri: null == favoriteUri 108 + ? _value.favoriteUri 109 + : favoriteUri // ignore: cast_nullable_to_non_nullable 110 + as String, 111 + ), 112 + ); 113 + } 114 + } 115 + 116 + /// @nodoc 117 + @JsonSerializable() 118 + class _$CreateFavoriteResponseImpl implements _CreateFavoriteResponse { 119 + const _$CreateFavoriteResponseImpl({required this.favoriteUri}); 120 + 121 + factory _$CreateFavoriteResponseImpl.fromJson(Map<String, dynamic> json) => 122 + _$$CreateFavoriteResponseImplFromJson(json); 123 + 124 + @override 125 + final String favoriteUri; 126 + 127 + @override 128 + String toString() { 129 + return 'CreateFavoriteResponse(favoriteUri: $favoriteUri)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$CreateFavoriteResponseImpl && 137 + (identical(other.favoriteUri, favoriteUri) || 138 + other.favoriteUri == favoriteUri)); 139 + } 140 + 141 + @JsonKey(includeFromJson: false, includeToJson: false) 142 + @override 143 + int get hashCode => Object.hash(runtimeType, favoriteUri); 144 + 145 + /// Create a copy of CreateFavoriteResponse 146 + /// with the given fields replaced by the non-null parameter values. 147 + @JsonKey(includeFromJson: false, includeToJson: false) 148 + @override 149 + @pragma('vm:prefer-inline') 150 + _$$CreateFavoriteResponseImplCopyWith<_$CreateFavoriteResponseImpl> 151 + get copyWith => 152 + __$$CreateFavoriteResponseImplCopyWithImpl<_$CreateFavoriteResponseImpl>( 153 + this, 154 + _$identity, 155 + ); 156 + 157 + @override 158 + Map<String, dynamic> toJson() { 159 + return _$$CreateFavoriteResponseImplToJson(this); 160 + } 161 + } 162 + 163 + abstract class _CreateFavoriteResponse implements CreateFavoriteResponse { 164 + const factory _CreateFavoriteResponse({required final String favoriteUri}) = 165 + _$CreateFavoriteResponseImpl; 166 + 167 + factory _CreateFavoriteResponse.fromJson(Map<String, dynamic> json) = 168 + _$CreateFavoriteResponseImpl.fromJson; 169 + 170 + @override 171 + String get favoriteUri; 172 + 173 + /// Create a copy of CreateFavoriteResponse 174 + /// with the given fields replaced by the non-null parameter values. 175 + @override 176 + @JsonKey(includeFromJson: false, includeToJson: false) 177 + _$$CreateFavoriteResponseImplCopyWith<_$CreateFavoriteResponseImpl> 178 + get copyWith => throw _privateConstructorUsedError; 179 + }
+15
lib/models/procedures/create_favorite_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_favorite_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateFavoriteResponseImpl _$$CreateFavoriteResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateFavoriteResponseImpl(favoriteUri: json['favoriteUri'] as String); 12 + 13 + Map<String, dynamic> _$$CreateFavoriteResponseImplToJson( 14 + _$CreateFavoriteResponseImpl instance, 15 + ) => <String, dynamic>{'favoriteUri': instance.favoriteUri};
+15
lib/models/procedures/create_follow_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_follow_request.freezed.dart'; 4 + part 'create_follow_request.g.dart'; 5 + 6 + /// Request model for creating a follow relationship. 7 + /// 8 + /// [subject] - The actor DID to follow. 9 + @freezed 10 + class CreateFollowRequest with _$CreateFollowRequest { 11 + const factory CreateFollowRequest({required String subject}) = _CreateFollowRequest; 12 + 13 + factory CreateFollowRequest.fromJson(Map<String, dynamic> json) => 14 + _$CreateFollowRequestFromJson(json); 15 + }
+171
lib/models/procedures/create_follow_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_follow_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateFollowRequest _$CreateFollowRequestFromJson(Map<String, dynamic> json) { 19 + return _CreateFollowRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$CreateFollowRequest { 24 + String get subject => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this CreateFollowRequest to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of CreateFollowRequest 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $CreateFollowRequestCopyWith<CreateFollowRequest> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $CreateFollowRequestCopyWith<$Res> { 38 + factory $CreateFollowRequestCopyWith( 39 + CreateFollowRequest value, 40 + $Res Function(CreateFollowRequest) then, 41 + ) = _$CreateFollowRequestCopyWithImpl<$Res, CreateFollowRequest>; 42 + @useResult 43 + $Res call({String subject}); 44 + } 45 + 46 + /// @nodoc 47 + class _$CreateFollowRequestCopyWithImpl<$Res, $Val extends CreateFollowRequest> 48 + implements $CreateFollowRequestCopyWith<$Res> { 49 + _$CreateFollowRequestCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of CreateFollowRequest 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? subject = null}) { 61 + return _then( 62 + _value.copyWith( 63 + subject: null == subject 64 + ? _value.subject 65 + : subject // ignore: cast_nullable_to_non_nullable 66 + as String, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$CreateFollowRequestImplCopyWith<$Res> 75 + implements $CreateFollowRequestCopyWith<$Res> { 76 + factory _$$CreateFollowRequestImplCopyWith( 77 + _$CreateFollowRequestImpl value, 78 + $Res Function(_$CreateFollowRequestImpl) then, 79 + ) = __$$CreateFollowRequestImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({String subject}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$CreateFollowRequestImplCopyWithImpl<$Res> 87 + extends _$CreateFollowRequestCopyWithImpl<$Res, _$CreateFollowRequestImpl> 88 + implements _$$CreateFollowRequestImplCopyWith<$Res> { 89 + __$$CreateFollowRequestImplCopyWithImpl( 90 + _$CreateFollowRequestImpl _value, 91 + $Res Function(_$CreateFollowRequestImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of CreateFollowRequest 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? subject = null}) { 99 + return _then( 100 + _$CreateFollowRequestImpl( 101 + subject: null == subject 102 + ? _value.subject 103 + : subject // ignore: cast_nullable_to_non_nullable 104 + as String, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$CreateFollowRequestImpl implements _CreateFollowRequest { 113 + const _$CreateFollowRequestImpl({required this.subject}); 114 + 115 + factory _$CreateFollowRequestImpl.fromJson(Map<String, dynamic> json) => 116 + _$$CreateFollowRequestImplFromJson(json); 117 + 118 + @override 119 + final String subject; 120 + 121 + @override 122 + String toString() { 123 + return 'CreateFollowRequest(subject: $subject)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$CreateFollowRequestImpl && 131 + (identical(other.subject, subject) || other.subject == subject)); 132 + } 133 + 134 + @JsonKey(includeFromJson: false, includeToJson: false) 135 + @override 136 + int get hashCode => Object.hash(runtimeType, subject); 137 + 138 + /// Create a copy of CreateFollowRequest 139 + /// with the given fields replaced by the non-null parameter values. 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + @pragma('vm:prefer-inline') 143 + _$$CreateFollowRequestImplCopyWith<_$CreateFollowRequestImpl> get copyWith => 144 + __$$CreateFollowRequestImplCopyWithImpl<_$CreateFollowRequestImpl>( 145 + this, 146 + _$identity, 147 + ); 148 + 149 + @override 150 + Map<String, dynamic> toJson() { 151 + return _$$CreateFollowRequestImplToJson(this); 152 + } 153 + } 154 + 155 + abstract class _CreateFollowRequest implements CreateFollowRequest { 156 + const factory _CreateFollowRequest({required final String subject}) = 157 + _$CreateFollowRequestImpl; 158 + 159 + factory _CreateFollowRequest.fromJson(Map<String, dynamic> json) = 160 + _$CreateFollowRequestImpl.fromJson; 161 + 162 + @override 163 + String get subject; 164 + 165 + /// Create a copy of CreateFollowRequest 166 + /// with the given fields replaced by the non-null parameter values. 167 + @override 168 + @JsonKey(includeFromJson: false, includeToJson: false) 169 + _$$CreateFollowRequestImplCopyWith<_$CreateFollowRequestImpl> get copyWith => 170 + throw _privateConstructorUsedError; 171 + }
+15
lib/models/procedures/create_follow_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_follow_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateFollowRequestImpl _$$CreateFollowRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateFollowRequestImpl(subject: json['subject'] as String); 12 + 13 + Map<String, dynamic> _$$CreateFollowRequestImplToJson( 14 + _$CreateFollowRequestImpl instance, 15 + ) => <String, dynamic>{'subject': instance.subject};
+15
lib/models/procedures/create_follow_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'create_follow_response.freezed.dart'; 4 + part 'create_follow_response.g.dart'; 5 + 6 + /// Response model for creating a follow relationship. 7 + /// 8 + /// [followUri] - The URI of the created follow relationship. 9 + @freezed 10 + class CreateFollowResponse with _$CreateFollowResponse { 11 + const factory CreateFollowResponse({required String followUri}) = _CreateFollowResponse; 12 + 13 + factory CreateFollowResponse.fromJson(Map<String, dynamic> json) => 14 + _$CreateFollowResponseFromJson(json); 15 + }
+176
lib/models/procedures/create_follow_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'create_follow_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + CreateFollowResponse _$CreateFollowResponseFromJson(Map<String, dynamic> json) { 19 + return _CreateFollowResponse.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$CreateFollowResponse { 24 + String get followUri => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this CreateFollowResponse to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of CreateFollowResponse 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $CreateFollowResponseCopyWith<CreateFollowResponse> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $CreateFollowResponseCopyWith<$Res> { 38 + factory $CreateFollowResponseCopyWith( 39 + CreateFollowResponse value, 40 + $Res Function(CreateFollowResponse) then, 41 + ) = _$CreateFollowResponseCopyWithImpl<$Res, CreateFollowResponse>; 42 + @useResult 43 + $Res call({String followUri}); 44 + } 45 + 46 + /// @nodoc 47 + class _$CreateFollowResponseCopyWithImpl< 48 + $Res, 49 + $Val extends CreateFollowResponse 50 + > 51 + implements $CreateFollowResponseCopyWith<$Res> { 52 + _$CreateFollowResponseCopyWithImpl(this._value, this._then); 53 + 54 + // ignore: unused_field 55 + final $Val _value; 56 + // ignore: unused_field 57 + final $Res Function($Val) _then; 58 + 59 + /// Create a copy of CreateFollowResponse 60 + /// with the given fields replaced by the non-null parameter values. 61 + @pragma('vm:prefer-inline') 62 + @override 63 + $Res call({Object? followUri = null}) { 64 + return _then( 65 + _value.copyWith( 66 + followUri: null == followUri 67 + ? _value.followUri 68 + : followUri // ignore: cast_nullable_to_non_nullable 69 + as String, 70 + ) 71 + as $Val, 72 + ); 73 + } 74 + } 75 + 76 + /// @nodoc 77 + abstract class _$$CreateFollowResponseImplCopyWith<$Res> 78 + implements $CreateFollowResponseCopyWith<$Res> { 79 + factory _$$CreateFollowResponseImplCopyWith( 80 + _$CreateFollowResponseImpl value, 81 + $Res Function(_$CreateFollowResponseImpl) then, 82 + ) = __$$CreateFollowResponseImplCopyWithImpl<$Res>; 83 + @override 84 + @useResult 85 + $Res call({String followUri}); 86 + } 87 + 88 + /// @nodoc 89 + class __$$CreateFollowResponseImplCopyWithImpl<$Res> 90 + extends _$CreateFollowResponseCopyWithImpl<$Res, _$CreateFollowResponseImpl> 91 + implements _$$CreateFollowResponseImplCopyWith<$Res> { 92 + __$$CreateFollowResponseImplCopyWithImpl( 93 + _$CreateFollowResponseImpl _value, 94 + $Res Function(_$CreateFollowResponseImpl) _then, 95 + ) : super(_value, _then); 96 + 97 + /// Create a copy of CreateFollowResponse 98 + /// with the given fields replaced by the non-null parameter values. 99 + @pragma('vm:prefer-inline') 100 + @override 101 + $Res call({Object? followUri = null}) { 102 + return _then( 103 + _$CreateFollowResponseImpl( 104 + followUri: null == followUri 105 + ? _value.followUri 106 + : followUri // ignore: cast_nullable_to_non_nullable 107 + as String, 108 + ), 109 + ); 110 + } 111 + } 112 + 113 + /// @nodoc 114 + @JsonSerializable() 115 + class _$CreateFollowResponseImpl implements _CreateFollowResponse { 116 + const _$CreateFollowResponseImpl({required this.followUri}); 117 + 118 + factory _$CreateFollowResponseImpl.fromJson(Map<String, dynamic> json) => 119 + _$$CreateFollowResponseImplFromJson(json); 120 + 121 + @override 122 + final String followUri; 123 + 124 + @override 125 + String toString() { 126 + return 'CreateFollowResponse(followUri: $followUri)'; 127 + } 128 + 129 + @override 130 + bool operator ==(Object other) { 131 + return identical(this, other) || 132 + (other.runtimeType == runtimeType && 133 + other is _$CreateFollowResponseImpl && 134 + (identical(other.followUri, followUri) || 135 + other.followUri == followUri)); 136 + } 137 + 138 + @JsonKey(includeFromJson: false, includeToJson: false) 139 + @override 140 + int get hashCode => Object.hash(runtimeType, followUri); 141 + 142 + /// Create a copy of CreateFollowResponse 143 + /// with the given fields replaced by the non-null parameter values. 144 + @JsonKey(includeFromJson: false, includeToJson: false) 145 + @override 146 + @pragma('vm:prefer-inline') 147 + _$$CreateFollowResponseImplCopyWith<_$CreateFollowResponseImpl> 148 + get copyWith => 149 + __$$CreateFollowResponseImplCopyWithImpl<_$CreateFollowResponseImpl>( 150 + this, 151 + _$identity, 152 + ); 153 + 154 + @override 155 + Map<String, dynamic> toJson() { 156 + return _$$CreateFollowResponseImplToJson(this); 157 + } 158 + } 159 + 160 + abstract class _CreateFollowResponse implements CreateFollowResponse { 161 + const factory _CreateFollowResponse({required final String followUri}) = 162 + _$CreateFollowResponseImpl; 163 + 164 + factory _CreateFollowResponse.fromJson(Map<String, dynamic> json) = 165 + _$CreateFollowResponseImpl.fromJson; 166 + 167 + @override 168 + String get followUri; 169 + 170 + /// Create a copy of CreateFollowResponse 171 + /// with the given fields replaced by the non-null parameter values. 172 + @override 173 + @JsonKey(includeFromJson: false, includeToJson: false) 174 + _$$CreateFollowResponseImplCopyWith<_$CreateFollowResponseImpl> 175 + get copyWith => throw _privateConstructorUsedError; 176 + }
+15
lib/models/procedures/create_follow_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'create_follow_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CreateFollowResponseImpl _$$CreateFollowResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$CreateFollowResponseImpl(followUri: json['followUri'] as String); 12 + 13 + Map<String, dynamic> _$$CreateFollowResponseImplToJson( 14 + _$CreateFollowResponseImpl instance, 15 + ) => <String, dynamic>{'followUri': instance.followUri};
+14
lib/models/procedures/delete_comment_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_comment_request.freezed.dart'; 4 + part 'delete_comment_request.g.dart'; 5 + 6 + /// Request model for deleting a comment. 7 + /// See lexicon: social.grain.comment.deleteComment 8 + @freezed 9 + class DeleteCommentRequest with _$DeleteCommentRequest { 10 + const factory DeleteCommentRequest({required String uri}) = _DeleteCommentRequest; 11 + 12 + factory DeleteCommentRequest.fromJson(Map<String, dynamic> json) => 13 + _$DeleteCommentRequestFromJson(json); 14 + }
+175
lib/models/procedures/delete_comment_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_comment_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeleteCommentRequest _$DeleteCommentRequestFromJson(Map<String, dynamic> json) { 19 + return _DeleteCommentRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$DeleteCommentRequest { 24 + String get uri => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this DeleteCommentRequest to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of DeleteCommentRequest 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $DeleteCommentRequestCopyWith<DeleteCommentRequest> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $DeleteCommentRequestCopyWith<$Res> { 38 + factory $DeleteCommentRequestCopyWith( 39 + DeleteCommentRequest value, 40 + $Res Function(DeleteCommentRequest) then, 41 + ) = _$DeleteCommentRequestCopyWithImpl<$Res, DeleteCommentRequest>; 42 + @useResult 43 + $Res call({String uri}); 44 + } 45 + 46 + /// @nodoc 47 + class _$DeleteCommentRequestCopyWithImpl< 48 + $Res, 49 + $Val extends DeleteCommentRequest 50 + > 51 + implements $DeleteCommentRequestCopyWith<$Res> { 52 + _$DeleteCommentRequestCopyWithImpl(this._value, this._then); 53 + 54 + // ignore: unused_field 55 + final $Val _value; 56 + // ignore: unused_field 57 + final $Res Function($Val) _then; 58 + 59 + /// Create a copy of DeleteCommentRequest 60 + /// with the given fields replaced by the non-null parameter values. 61 + @pragma('vm:prefer-inline') 62 + @override 63 + $Res call({Object? uri = null}) { 64 + return _then( 65 + _value.copyWith( 66 + uri: null == uri 67 + ? _value.uri 68 + : uri // ignore: cast_nullable_to_non_nullable 69 + as String, 70 + ) 71 + as $Val, 72 + ); 73 + } 74 + } 75 + 76 + /// @nodoc 77 + abstract class _$$DeleteCommentRequestImplCopyWith<$Res> 78 + implements $DeleteCommentRequestCopyWith<$Res> { 79 + factory _$$DeleteCommentRequestImplCopyWith( 80 + _$DeleteCommentRequestImpl value, 81 + $Res Function(_$DeleteCommentRequestImpl) then, 82 + ) = __$$DeleteCommentRequestImplCopyWithImpl<$Res>; 83 + @override 84 + @useResult 85 + $Res call({String uri}); 86 + } 87 + 88 + /// @nodoc 89 + class __$$DeleteCommentRequestImplCopyWithImpl<$Res> 90 + extends _$DeleteCommentRequestCopyWithImpl<$Res, _$DeleteCommentRequestImpl> 91 + implements _$$DeleteCommentRequestImplCopyWith<$Res> { 92 + __$$DeleteCommentRequestImplCopyWithImpl( 93 + _$DeleteCommentRequestImpl _value, 94 + $Res Function(_$DeleteCommentRequestImpl) _then, 95 + ) : super(_value, _then); 96 + 97 + /// Create a copy of DeleteCommentRequest 98 + /// with the given fields replaced by the non-null parameter values. 99 + @pragma('vm:prefer-inline') 100 + @override 101 + $Res call({Object? uri = null}) { 102 + return _then( 103 + _$DeleteCommentRequestImpl( 104 + uri: null == uri 105 + ? _value.uri 106 + : uri // ignore: cast_nullable_to_non_nullable 107 + as String, 108 + ), 109 + ); 110 + } 111 + } 112 + 113 + /// @nodoc 114 + @JsonSerializable() 115 + class _$DeleteCommentRequestImpl implements _DeleteCommentRequest { 116 + const _$DeleteCommentRequestImpl({required this.uri}); 117 + 118 + factory _$DeleteCommentRequestImpl.fromJson(Map<String, dynamic> json) => 119 + _$$DeleteCommentRequestImplFromJson(json); 120 + 121 + @override 122 + final String uri; 123 + 124 + @override 125 + String toString() { 126 + return 'DeleteCommentRequest(uri: $uri)'; 127 + } 128 + 129 + @override 130 + bool operator ==(Object other) { 131 + return identical(this, other) || 132 + (other.runtimeType == runtimeType && 133 + other is _$DeleteCommentRequestImpl && 134 + (identical(other.uri, uri) || other.uri == uri)); 135 + } 136 + 137 + @JsonKey(includeFromJson: false, includeToJson: false) 138 + @override 139 + int get hashCode => Object.hash(runtimeType, uri); 140 + 141 + /// Create a copy of DeleteCommentRequest 142 + /// with the given fields replaced by the non-null parameter values. 143 + @JsonKey(includeFromJson: false, includeToJson: false) 144 + @override 145 + @pragma('vm:prefer-inline') 146 + _$$DeleteCommentRequestImplCopyWith<_$DeleteCommentRequestImpl> 147 + get copyWith => 148 + __$$DeleteCommentRequestImplCopyWithImpl<_$DeleteCommentRequestImpl>( 149 + this, 150 + _$identity, 151 + ); 152 + 153 + @override 154 + Map<String, dynamic> toJson() { 155 + return _$$DeleteCommentRequestImplToJson(this); 156 + } 157 + } 158 + 159 + abstract class _DeleteCommentRequest implements DeleteCommentRequest { 160 + const factory _DeleteCommentRequest({required final String uri}) = 161 + _$DeleteCommentRequestImpl; 162 + 163 + factory _DeleteCommentRequest.fromJson(Map<String, dynamic> json) = 164 + _$DeleteCommentRequestImpl.fromJson; 165 + 166 + @override 167 + String get uri; 168 + 169 + /// Create a copy of DeleteCommentRequest 170 + /// with the given fields replaced by the non-null parameter values. 171 + @override 172 + @JsonKey(includeFromJson: false, includeToJson: false) 173 + _$$DeleteCommentRequestImplCopyWith<_$DeleteCommentRequestImpl> 174 + get copyWith => throw _privateConstructorUsedError; 175 + }
+15
lib/models/procedures/delete_comment_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_comment_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeleteCommentRequestImpl _$$DeleteCommentRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeleteCommentRequestImpl(uri: json['uri'] as String); 12 + 13 + Map<String, dynamic> _$$DeleteCommentRequestImplToJson( 14 + _$DeleteCommentRequestImpl instance, 15 + ) => <String, dynamic>{'uri': instance.uri};
+14
lib/models/procedures/delete_comment_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_comment_response.freezed.dart'; 4 + part 'delete_comment_response.g.dart'; 5 + 6 + /// Response model for deleting a comment. 7 + /// See lexicon: social.grain.comment.deleteComment 8 + @freezed 9 + class DeleteCommentResponse with _$DeleteCommentResponse { 10 + const factory DeleteCommentResponse({required bool success}) = _DeleteCommentResponse; 11 + 12 + factory DeleteCommentResponse.fromJson(Map<String, dynamic> json) => 13 + _$DeleteCommentResponseFromJson(json); 14 + }
+178
lib/models/procedures/delete_comment_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_comment_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeleteCommentResponse _$DeleteCommentResponseFromJson( 19 + Map<String, dynamic> json, 20 + ) { 21 + return _DeleteCommentResponse.fromJson(json); 22 + } 23 + 24 + /// @nodoc 25 + mixin _$DeleteCommentResponse { 26 + bool get success => throw _privateConstructorUsedError; 27 + 28 + /// Serializes this DeleteCommentResponse to a JSON map. 29 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 30 + 31 + /// Create a copy of DeleteCommentResponse 32 + /// with the given fields replaced by the non-null parameter values. 33 + @JsonKey(includeFromJson: false, includeToJson: false) 34 + $DeleteCommentResponseCopyWith<DeleteCommentResponse> get copyWith => 35 + throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $DeleteCommentResponseCopyWith<$Res> { 40 + factory $DeleteCommentResponseCopyWith( 41 + DeleteCommentResponse value, 42 + $Res Function(DeleteCommentResponse) then, 43 + ) = _$DeleteCommentResponseCopyWithImpl<$Res, DeleteCommentResponse>; 44 + @useResult 45 + $Res call({bool success}); 46 + } 47 + 48 + /// @nodoc 49 + class _$DeleteCommentResponseCopyWithImpl< 50 + $Res, 51 + $Val extends DeleteCommentResponse 52 + > 53 + implements $DeleteCommentResponseCopyWith<$Res> { 54 + _$DeleteCommentResponseCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of DeleteCommentResponse 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({Object? success = null}) { 66 + return _then( 67 + _value.copyWith( 68 + success: null == success 69 + ? _value.success 70 + : success // ignore: cast_nullable_to_non_nullable 71 + as bool, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$DeleteCommentResponseImplCopyWith<$Res> 80 + implements $DeleteCommentResponseCopyWith<$Res> { 81 + factory _$$DeleteCommentResponseImplCopyWith( 82 + _$DeleteCommentResponseImpl value, 83 + $Res Function(_$DeleteCommentResponseImpl) then, 84 + ) = __$$DeleteCommentResponseImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({bool success}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$DeleteCommentResponseImplCopyWithImpl<$Res> 92 + extends 93 + _$DeleteCommentResponseCopyWithImpl<$Res, _$DeleteCommentResponseImpl> 94 + implements _$$DeleteCommentResponseImplCopyWith<$Res> { 95 + __$$DeleteCommentResponseImplCopyWithImpl( 96 + _$DeleteCommentResponseImpl _value, 97 + $Res Function(_$DeleteCommentResponseImpl) _then, 98 + ) : super(_value, _then); 99 + 100 + /// Create a copy of DeleteCommentResponse 101 + /// with the given fields replaced by the non-null parameter values. 102 + @pragma('vm:prefer-inline') 103 + @override 104 + $Res call({Object? success = null}) { 105 + return _then( 106 + _$DeleteCommentResponseImpl( 107 + success: null == success 108 + ? _value.success 109 + : success // ignore: cast_nullable_to_non_nullable 110 + as bool, 111 + ), 112 + ); 113 + } 114 + } 115 + 116 + /// @nodoc 117 + @JsonSerializable() 118 + class _$DeleteCommentResponseImpl implements _DeleteCommentResponse { 119 + const _$DeleteCommentResponseImpl({required this.success}); 120 + 121 + factory _$DeleteCommentResponseImpl.fromJson(Map<String, dynamic> json) => 122 + _$$DeleteCommentResponseImplFromJson(json); 123 + 124 + @override 125 + final bool success; 126 + 127 + @override 128 + String toString() { 129 + return 'DeleteCommentResponse(success: $success)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$DeleteCommentResponseImpl && 137 + (identical(other.success, success) || other.success == success)); 138 + } 139 + 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + int get hashCode => Object.hash(runtimeType, success); 143 + 144 + /// Create a copy of DeleteCommentResponse 145 + /// with the given fields replaced by the non-null parameter values. 146 + @JsonKey(includeFromJson: false, includeToJson: false) 147 + @override 148 + @pragma('vm:prefer-inline') 149 + _$$DeleteCommentResponseImplCopyWith<_$DeleteCommentResponseImpl> 150 + get copyWith => 151 + __$$DeleteCommentResponseImplCopyWithImpl<_$DeleteCommentResponseImpl>( 152 + this, 153 + _$identity, 154 + ); 155 + 156 + @override 157 + Map<String, dynamic> toJson() { 158 + return _$$DeleteCommentResponseImplToJson(this); 159 + } 160 + } 161 + 162 + abstract class _DeleteCommentResponse implements DeleteCommentResponse { 163 + const factory _DeleteCommentResponse({required final bool success}) = 164 + _$DeleteCommentResponseImpl; 165 + 166 + factory _DeleteCommentResponse.fromJson(Map<String, dynamic> json) = 167 + _$DeleteCommentResponseImpl.fromJson; 168 + 169 + @override 170 + bool get success; 171 + 172 + /// Create a copy of DeleteCommentResponse 173 + /// with the given fields replaced by the non-null parameter values. 174 + @override 175 + @JsonKey(includeFromJson: false, includeToJson: false) 176 + _$$DeleteCommentResponseImplCopyWith<_$DeleteCommentResponseImpl> 177 + get copyWith => throw _privateConstructorUsedError; 178 + }
+15
lib/models/procedures/delete_comment_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_comment_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeleteCommentResponseImpl _$$DeleteCommentResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeleteCommentResponseImpl(success: json['success'] as bool); 12 + 13 + Map<String, dynamic> _$$DeleteCommentResponseImplToJson( 14 + _$DeleteCommentResponseImpl instance, 15 + ) => <String, dynamic>{'success': instance.success};
+14
lib/models/procedures/delete_favorite_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_favorite_request.freezed.dart'; 4 + part 'delete_favorite_request.g.dart'; 5 + 6 + /// Request model for deleting a favorite. 7 + /// See lexicon: social.grain.favorite.deleteFavorite 8 + @freezed 9 + class DeleteFavoriteRequest with _$DeleteFavoriteRequest { 10 + const factory DeleteFavoriteRequest({required String uri}) = _DeleteFavoriteRequest; 11 + 12 + factory DeleteFavoriteRequest.fromJson(Map<String, dynamic> json) => 13 + _$DeleteFavoriteRequestFromJson(json); 14 + }
+178
lib/models/procedures/delete_favorite_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_favorite_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeleteFavoriteRequest _$DeleteFavoriteRequestFromJson( 19 + Map<String, dynamic> json, 20 + ) { 21 + return _DeleteFavoriteRequest.fromJson(json); 22 + } 23 + 24 + /// @nodoc 25 + mixin _$DeleteFavoriteRequest { 26 + String get uri => throw _privateConstructorUsedError; 27 + 28 + /// Serializes this DeleteFavoriteRequest to a JSON map. 29 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 30 + 31 + /// Create a copy of DeleteFavoriteRequest 32 + /// with the given fields replaced by the non-null parameter values. 33 + @JsonKey(includeFromJson: false, includeToJson: false) 34 + $DeleteFavoriteRequestCopyWith<DeleteFavoriteRequest> get copyWith => 35 + throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $DeleteFavoriteRequestCopyWith<$Res> { 40 + factory $DeleteFavoriteRequestCopyWith( 41 + DeleteFavoriteRequest value, 42 + $Res Function(DeleteFavoriteRequest) then, 43 + ) = _$DeleteFavoriteRequestCopyWithImpl<$Res, DeleteFavoriteRequest>; 44 + @useResult 45 + $Res call({String uri}); 46 + } 47 + 48 + /// @nodoc 49 + class _$DeleteFavoriteRequestCopyWithImpl< 50 + $Res, 51 + $Val extends DeleteFavoriteRequest 52 + > 53 + implements $DeleteFavoriteRequestCopyWith<$Res> { 54 + _$DeleteFavoriteRequestCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of DeleteFavoriteRequest 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({Object? uri = null}) { 66 + return _then( 67 + _value.copyWith( 68 + uri: null == uri 69 + ? _value.uri 70 + : uri // ignore: cast_nullable_to_non_nullable 71 + as String, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$DeleteFavoriteRequestImplCopyWith<$Res> 80 + implements $DeleteFavoriteRequestCopyWith<$Res> { 81 + factory _$$DeleteFavoriteRequestImplCopyWith( 82 + _$DeleteFavoriteRequestImpl value, 83 + $Res Function(_$DeleteFavoriteRequestImpl) then, 84 + ) = __$$DeleteFavoriteRequestImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({String uri}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$DeleteFavoriteRequestImplCopyWithImpl<$Res> 92 + extends 93 + _$DeleteFavoriteRequestCopyWithImpl<$Res, _$DeleteFavoriteRequestImpl> 94 + implements _$$DeleteFavoriteRequestImplCopyWith<$Res> { 95 + __$$DeleteFavoriteRequestImplCopyWithImpl( 96 + _$DeleteFavoriteRequestImpl _value, 97 + $Res Function(_$DeleteFavoriteRequestImpl) _then, 98 + ) : super(_value, _then); 99 + 100 + /// Create a copy of DeleteFavoriteRequest 101 + /// with the given fields replaced by the non-null parameter values. 102 + @pragma('vm:prefer-inline') 103 + @override 104 + $Res call({Object? uri = null}) { 105 + return _then( 106 + _$DeleteFavoriteRequestImpl( 107 + uri: null == uri 108 + ? _value.uri 109 + : uri // ignore: cast_nullable_to_non_nullable 110 + as String, 111 + ), 112 + ); 113 + } 114 + } 115 + 116 + /// @nodoc 117 + @JsonSerializable() 118 + class _$DeleteFavoriteRequestImpl implements _DeleteFavoriteRequest { 119 + const _$DeleteFavoriteRequestImpl({required this.uri}); 120 + 121 + factory _$DeleteFavoriteRequestImpl.fromJson(Map<String, dynamic> json) => 122 + _$$DeleteFavoriteRequestImplFromJson(json); 123 + 124 + @override 125 + final String uri; 126 + 127 + @override 128 + String toString() { 129 + return 'DeleteFavoriteRequest(uri: $uri)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$DeleteFavoriteRequestImpl && 137 + (identical(other.uri, uri) || other.uri == uri)); 138 + } 139 + 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + int get hashCode => Object.hash(runtimeType, uri); 143 + 144 + /// Create a copy of DeleteFavoriteRequest 145 + /// with the given fields replaced by the non-null parameter values. 146 + @JsonKey(includeFromJson: false, includeToJson: false) 147 + @override 148 + @pragma('vm:prefer-inline') 149 + _$$DeleteFavoriteRequestImplCopyWith<_$DeleteFavoriteRequestImpl> 150 + get copyWith => 151 + __$$DeleteFavoriteRequestImplCopyWithImpl<_$DeleteFavoriteRequestImpl>( 152 + this, 153 + _$identity, 154 + ); 155 + 156 + @override 157 + Map<String, dynamic> toJson() { 158 + return _$$DeleteFavoriteRequestImplToJson(this); 159 + } 160 + } 161 + 162 + abstract class _DeleteFavoriteRequest implements DeleteFavoriteRequest { 163 + const factory _DeleteFavoriteRequest({required final String uri}) = 164 + _$DeleteFavoriteRequestImpl; 165 + 166 + factory _DeleteFavoriteRequest.fromJson(Map<String, dynamic> json) = 167 + _$DeleteFavoriteRequestImpl.fromJson; 168 + 169 + @override 170 + String get uri; 171 + 172 + /// Create a copy of DeleteFavoriteRequest 173 + /// with the given fields replaced by the non-null parameter values. 174 + @override 175 + @JsonKey(includeFromJson: false, includeToJson: false) 176 + _$$DeleteFavoriteRequestImplCopyWith<_$DeleteFavoriteRequestImpl> 177 + get copyWith => throw _privateConstructorUsedError; 178 + }
+15
lib/models/procedures/delete_favorite_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_favorite_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeleteFavoriteRequestImpl _$$DeleteFavoriteRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeleteFavoriteRequestImpl(uri: json['uri'] as String); 12 + 13 + Map<String, dynamic> _$$DeleteFavoriteRequestImplToJson( 14 + _$DeleteFavoriteRequestImpl instance, 15 + ) => <String, dynamic>{'uri': instance.uri};
+14
lib/models/procedures/delete_favorite_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_favorite_response.freezed.dart'; 4 + part 'delete_favorite_response.g.dart'; 5 + 6 + /// Response model for deleting a favorite. 7 + /// See lexicon: social.grain.favorite.deleteFavorite 8 + @freezed 9 + class DeleteFavoriteResponse with _$DeleteFavoriteResponse { 10 + const factory DeleteFavoriteResponse({required bool success}) = _DeleteFavoriteResponse; 11 + 12 + factory DeleteFavoriteResponse.fromJson(Map<String, dynamic> json) => 13 + _$DeleteFavoriteResponseFromJson(json); 14 + }
+178
lib/models/procedures/delete_favorite_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_favorite_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeleteFavoriteResponse _$DeleteFavoriteResponseFromJson( 19 + Map<String, dynamic> json, 20 + ) { 21 + return _DeleteFavoriteResponse.fromJson(json); 22 + } 23 + 24 + /// @nodoc 25 + mixin _$DeleteFavoriteResponse { 26 + bool get success => throw _privateConstructorUsedError; 27 + 28 + /// Serializes this DeleteFavoriteResponse to a JSON map. 29 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 30 + 31 + /// Create a copy of DeleteFavoriteResponse 32 + /// with the given fields replaced by the non-null parameter values. 33 + @JsonKey(includeFromJson: false, includeToJson: false) 34 + $DeleteFavoriteResponseCopyWith<DeleteFavoriteResponse> get copyWith => 35 + throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $DeleteFavoriteResponseCopyWith<$Res> { 40 + factory $DeleteFavoriteResponseCopyWith( 41 + DeleteFavoriteResponse value, 42 + $Res Function(DeleteFavoriteResponse) then, 43 + ) = _$DeleteFavoriteResponseCopyWithImpl<$Res, DeleteFavoriteResponse>; 44 + @useResult 45 + $Res call({bool success}); 46 + } 47 + 48 + /// @nodoc 49 + class _$DeleteFavoriteResponseCopyWithImpl< 50 + $Res, 51 + $Val extends DeleteFavoriteResponse 52 + > 53 + implements $DeleteFavoriteResponseCopyWith<$Res> { 54 + _$DeleteFavoriteResponseCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of DeleteFavoriteResponse 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({Object? success = null}) { 66 + return _then( 67 + _value.copyWith( 68 + success: null == success 69 + ? _value.success 70 + : success // ignore: cast_nullable_to_non_nullable 71 + as bool, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$DeleteFavoriteResponseImplCopyWith<$Res> 80 + implements $DeleteFavoriteResponseCopyWith<$Res> { 81 + factory _$$DeleteFavoriteResponseImplCopyWith( 82 + _$DeleteFavoriteResponseImpl value, 83 + $Res Function(_$DeleteFavoriteResponseImpl) then, 84 + ) = __$$DeleteFavoriteResponseImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({bool success}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$DeleteFavoriteResponseImplCopyWithImpl<$Res> 92 + extends 93 + _$DeleteFavoriteResponseCopyWithImpl<$Res, _$DeleteFavoriteResponseImpl> 94 + implements _$$DeleteFavoriteResponseImplCopyWith<$Res> { 95 + __$$DeleteFavoriteResponseImplCopyWithImpl( 96 + _$DeleteFavoriteResponseImpl _value, 97 + $Res Function(_$DeleteFavoriteResponseImpl) _then, 98 + ) : super(_value, _then); 99 + 100 + /// Create a copy of DeleteFavoriteResponse 101 + /// with the given fields replaced by the non-null parameter values. 102 + @pragma('vm:prefer-inline') 103 + @override 104 + $Res call({Object? success = null}) { 105 + return _then( 106 + _$DeleteFavoriteResponseImpl( 107 + success: null == success 108 + ? _value.success 109 + : success // ignore: cast_nullable_to_non_nullable 110 + as bool, 111 + ), 112 + ); 113 + } 114 + } 115 + 116 + /// @nodoc 117 + @JsonSerializable() 118 + class _$DeleteFavoriteResponseImpl implements _DeleteFavoriteResponse { 119 + const _$DeleteFavoriteResponseImpl({required this.success}); 120 + 121 + factory _$DeleteFavoriteResponseImpl.fromJson(Map<String, dynamic> json) => 122 + _$$DeleteFavoriteResponseImplFromJson(json); 123 + 124 + @override 125 + final bool success; 126 + 127 + @override 128 + String toString() { 129 + return 'DeleteFavoriteResponse(success: $success)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$DeleteFavoriteResponseImpl && 137 + (identical(other.success, success) || other.success == success)); 138 + } 139 + 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + int get hashCode => Object.hash(runtimeType, success); 143 + 144 + /// Create a copy of DeleteFavoriteResponse 145 + /// with the given fields replaced by the non-null parameter values. 146 + @JsonKey(includeFromJson: false, includeToJson: false) 147 + @override 148 + @pragma('vm:prefer-inline') 149 + _$$DeleteFavoriteResponseImplCopyWith<_$DeleteFavoriteResponseImpl> 150 + get copyWith => 151 + __$$DeleteFavoriteResponseImplCopyWithImpl<_$DeleteFavoriteResponseImpl>( 152 + this, 153 + _$identity, 154 + ); 155 + 156 + @override 157 + Map<String, dynamic> toJson() { 158 + return _$$DeleteFavoriteResponseImplToJson(this); 159 + } 160 + } 161 + 162 + abstract class _DeleteFavoriteResponse implements DeleteFavoriteResponse { 163 + const factory _DeleteFavoriteResponse({required final bool success}) = 164 + _$DeleteFavoriteResponseImpl; 165 + 166 + factory _DeleteFavoriteResponse.fromJson(Map<String, dynamic> json) = 167 + _$DeleteFavoriteResponseImpl.fromJson; 168 + 169 + @override 170 + bool get success; 171 + 172 + /// Create a copy of DeleteFavoriteResponse 173 + /// with the given fields replaced by the non-null parameter values. 174 + @override 175 + @JsonKey(includeFromJson: false, includeToJson: false) 176 + _$$DeleteFavoriteResponseImplCopyWith<_$DeleteFavoriteResponseImpl> 177 + get copyWith => throw _privateConstructorUsedError; 178 + }
+15
lib/models/procedures/delete_favorite_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_favorite_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeleteFavoriteResponseImpl _$$DeleteFavoriteResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeleteFavoriteResponseImpl(success: json['success'] as bool); 12 + 13 + Map<String, dynamic> _$$DeleteFavoriteResponseImplToJson( 14 + _$DeleteFavoriteResponseImpl instance, 15 + ) => <String, dynamic>{'success': instance.success};
+15
lib/models/procedures/delete_follow_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_follow_request.freezed.dart'; 4 + part 'delete_follow_request.g.dart'; 5 + 6 + /// Request model for deleting a follow relationship. 7 + /// 8 + /// [uri] - The URI of the follow relationship to delete. 9 + @freezed 10 + class DeleteFollowRequest with _$DeleteFollowRequest { 11 + const factory DeleteFollowRequest({required String uri}) = _DeleteFollowRequest; 12 + 13 + factory DeleteFollowRequest.fromJson(Map<String, dynamic> json) => 14 + _$DeleteFollowRequestFromJson(json); 15 + }
+171
lib/models/procedures/delete_follow_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_follow_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeleteFollowRequest _$DeleteFollowRequestFromJson(Map<String, dynamic> json) { 19 + return _DeleteFollowRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$DeleteFollowRequest { 24 + String get uri => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this DeleteFollowRequest to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of DeleteFollowRequest 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $DeleteFollowRequestCopyWith<DeleteFollowRequest> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $DeleteFollowRequestCopyWith<$Res> { 38 + factory $DeleteFollowRequestCopyWith( 39 + DeleteFollowRequest value, 40 + $Res Function(DeleteFollowRequest) then, 41 + ) = _$DeleteFollowRequestCopyWithImpl<$Res, DeleteFollowRequest>; 42 + @useResult 43 + $Res call({String uri}); 44 + } 45 + 46 + /// @nodoc 47 + class _$DeleteFollowRequestCopyWithImpl<$Res, $Val extends DeleteFollowRequest> 48 + implements $DeleteFollowRequestCopyWith<$Res> { 49 + _$DeleteFollowRequestCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of DeleteFollowRequest 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? uri = null}) { 61 + return _then( 62 + _value.copyWith( 63 + uri: null == uri 64 + ? _value.uri 65 + : uri // ignore: cast_nullable_to_non_nullable 66 + as String, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$DeleteFollowRequestImplCopyWith<$Res> 75 + implements $DeleteFollowRequestCopyWith<$Res> { 76 + factory _$$DeleteFollowRequestImplCopyWith( 77 + _$DeleteFollowRequestImpl value, 78 + $Res Function(_$DeleteFollowRequestImpl) then, 79 + ) = __$$DeleteFollowRequestImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({String uri}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$DeleteFollowRequestImplCopyWithImpl<$Res> 87 + extends _$DeleteFollowRequestCopyWithImpl<$Res, _$DeleteFollowRequestImpl> 88 + implements _$$DeleteFollowRequestImplCopyWith<$Res> { 89 + __$$DeleteFollowRequestImplCopyWithImpl( 90 + _$DeleteFollowRequestImpl _value, 91 + $Res Function(_$DeleteFollowRequestImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of DeleteFollowRequest 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? uri = null}) { 99 + return _then( 100 + _$DeleteFollowRequestImpl( 101 + uri: null == uri 102 + ? _value.uri 103 + : uri // ignore: cast_nullable_to_non_nullable 104 + as String, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$DeleteFollowRequestImpl implements _DeleteFollowRequest { 113 + const _$DeleteFollowRequestImpl({required this.uri}); 114 + 115 + factory _$DeleteFollowRequestImpl.fromJson(Map<String, dynamic> json) => 116 + _$$DeleteFollowRequestImplFromJson(json); 117 + 118 + @override 119 + final String uri; 120 + 121 + @override 122 + String toString() { 123 + return 'DeleteFollowRequest(uri: $uri)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$DeleteFollowRequestImpl && 131 + (identical(other.uri, uri) || other.uri == uri)); 132 + } 133 + 134 + @JsonKey(includeFromJson: false, includeToJson: false) 135 + @override 136 + int get hashCode => Object.hash(runtimeType, uri); 137 + 138 + /// Create a copy of DeleteFollowRequest 139 + /// with the given fields replaced by the non-null parameter values. 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + @pragma('vm:prefer-inline') 143 + _$$DeleteFollowRequestImplCopyWith<_$DeleteFollowRequestImpl> get copyWith => 144 + __$$DeleteFollowRequestImplCopyWithImpl<_$DeleteFollowRequestImpl>( 145 + this, 146 + _$identity, 147 + ); 148 + 149 + @override 150 + Map<String, dynamic> toJson() { 151 + return _$$DeleteFollowRequestImplToJson(this); 152 + } 153 + } 154 + 155 + abstract class _DeleteFollowRequest implements DeleteFollowRequest { 156 + const factory _DeleteFollowRequest({required final String uri}) = 157 + _$DeleteFollowRequestImpl; 158 + 159 + factory _DeleteFollowRequest.fromJson(Map<String, dynamic> json) = 160 + _$DeleteFollowRequestImpl.fromJson; 161 + 162 + @override 163 + String get uri; 164 + 165 + /// Create a copy of DeleteFollowRequest 166 + /// with the given fields replaced by the non-null parameter values. 167 + @override 168 + @JsonKey(includeFromJson: false, includeToJson: false) 169 + _$$DeleteFollowRequestImplCopyWith<_$DeleteFollowRequestImpl> get copyWith => 170 + throw _privateConstructorUsedError; 171 + }
+15
lib/models/procedures/delete_follow_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_follow_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeleteFollowRequestImpl _$$DeleteFollowRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeleteFollowRequestImpl(uri: json['uri'] as String); 12 + 13 + Map<String, dynamic> _$$DeleteFollowRequestImplToJson( 14 + _$DeleteFollowRequestImpl instance, 15 + ) => <String, dynamic>{'uri': instance.uri};
+15
lib/models/procedures/delete_follow_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_follow_response.freezed.dart'; 4 + part 'delete_follow_response.g.dart'; 5 + 6 + /// Response model for deleting a follow relationship. 7 + /// 8 + /// [success] - Indicates if the deletion was successful. 9 + @freezed 10 + class DeleteFollowResponse with _$DeleteFollowResponse { 11 + const factory DeleteFollowResponse({required bool success}) = _DeleteFollowResponse; 12 + 13 + factory DeleteFollowResponse.fromJson(Map<String, dynamic> json) => 14 + _$DeleteFollowResponseFromJson(json); 15 + }
+175
lib/models/procedures/delete_follow_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_follow_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeleteFollowResponse _$DeleteFollowResponseFromJson(Map<String, dynamic> json) { 19 + return _DeleteFollowResponse.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$DeleteFollowResponse { 24 + bool get success => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this DeleteFollowResponse to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of DeleteFollowResponse 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $DeleteFollowResponseCopyWith<DeleteFollowResponse> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $DeleteFollowResponseCopyWith<$Res> { 38 + factory $DeleteFollowResponseCopyWith( 39 + DeleteFollowResponse value, 40 + $Res Function(DeleteFollowResponse) then, 41 + ) = _$DeleteFollowResponseCopyWithImpl<$Res, DeleteFollowResponse>; 42 + @useResult 43 + $Res call({bool success}); 44 + } 45 + 46 + /// @nodoc 47 + class _$DeleteFollowResponseCopyWithImpl< 48 + $Res, 49 + $Val extends DeleteFollowResponse 50 + > 51 + implements $DeleteFollowResponseCopyWith<$Res> { 52 + _$DeleteFollowResponseCopyWithImpl(this._value, this._then); 53 + 54 + // ignore: unused_field 55 + final $Val _value; 56 + // ignore: unused_field 57 + final $Res Function($Val) _then; 58 + 59 + /// Create a copy of DeleteFollowResponse 60 + /// with the given fields replaced by the non-null parameter values. 61 + @pragma('vm:prefer-inline') 62 + @override 63 + $Res call({Object? success = null}) { 64 + return _then( 65 + _value.copyWith( 66 + success: null == success 67 + ? _value.success 68 + : success // ignore: cast_nullable_to_non_nullable 69 + as bool, 70 + ) 71 + as $Val, 72 + ); 73 + } 74 + } 75 + 76 + /// @nodoc 77 + abstract class _$$DeleteFollowResponseImplCopyWith<$Res> 78 + implements $DeleteFollowResponseCopyWith<$Res> { 79 + factory _$$DeleteFollowResponseImplCopyWith( 80 + _$DeleteFollowResponseImpl value, 81 + $Res Function(_$DeleteFollowResponseImpl) then, 82 + ) = __$$DeleteFollowResponseImplCopyWithImpl<$Res>; 83 + @override 84 + @useResult 85 + $Res call({bool success}); 86 + } 87 + 88 + /// @nodoc 89 + class __$$DeleteFollowResponseImplCopyWithImpl<$Res> 90 + extends _$DeleteFollowResponseCopyWithImpl<$Res, _$DeleteFollowResponseImpl> 91 + implements _$$DeleteFollowResponseImplCopyWith<$Res> { 92 + __$$DeleteFollowResponseImplCopyWithImpl( 93 + _$DeleteFollowResponseImpl _value, 94 + $Res Function(_$DeleteFollowResponseImpl) _then, 95 + ) : super(_value, _then); 96 + 97 + /// Create a copy of DeleteFollowResponse 98 + /// with the given fields replaced by the non-null parameter values. 99 + @pragma('vm:prefer-inline') 100 + @override 101 + $Res call({Object? success = null}) { 102 + return _then( 103 + _$DeleteFollowResponseImpl( 104 + success: null == success 105 + ? _value.success 106 + : success // ignore: cast_nullable_to_non_nullable 107 + as bool, 108 + ), 109 + ); 110 + } 111 + } 112 + 113 + /// @nodoc 114 + @JsonSerializable() 115 + class _$DeleteFollowResponseImpl implements _DeleteFollowResponse { 116 + const _$DeleteFollowResponseImpl({required this.success}); 117 + 118 + factory _$DeleteFollowResponseImpl.fromJson(Map<String, dynamic> json) => 119 + _$$DeleteFollowResponseImplFromJson(json); 120 + 121 + @override 122 + final bool success; 123 + 124 + @override 125 + String toString() { 126 + return 'DeleteFollowResponse(success: $success)'; 127 + } 128 + 129 + @override 130 + bool operator ==(Object other) { 131 + return identical(this, other) || 132 + (other.runtimeType == runtimeType && 133 + other is _$DeleteFollowResponseImpl && 134 + (identical(other.success, success) || other.success == success)); 135 + } 136 + 137 + @JsonKey(includeFromJson: false, includeToJson: false) 138 + @override 139 + int get hashCode => Object.hash(runtimeType, success); 140 + 141 + /// Create a copy of DeleteFollowResponse 142 + /// with the given fields replaced by the non-null parameter values. 143 + @JsonKey(includeFromJson: false, includeToJson: false) 144 + @override 145 + @pragma('vm:prefer-inline') 146 + _$$DeleteFollowResponseImplCopyWith<_$DeleteFollowResponseImpl> 147 + get copyWith => 148 + __$$DeleteFollowResponseImplCopyWithImpl<_$DeleteFollowResponseImpl>( 149 + this, 150 + _$identity, 151 + ); 152 + 153 + @override 154 + Map<String, dynamic> toJson() { 155 + return _$$DeleteFollowResponseImplToJson(this); 156 + } 157 + } 158 + 159 + abstract class _DeleteFollowResponse implements DeleteFollowResponse { 160 + const factory _DeleteFollowResponse({required final bool success}) = 161 + _$DeleteFollowResponseImpl; 162 + 163 + factory _DeleteFollowResponse.fromJson(Map<String, dynamic> json) = 164 + _$DeleteFollowResponseImpl.fromJson; 165 + 166 + @override 167 + bool get success; 168 + 169 + /// Create a copy of DeleteFollowResponse 170 + /// with the given fields replaced by the non-null parameter values. 171 + @override 172 + @JsonKey(includeFromJson: false, includeToJson: false) 173 + _$$DeleteFollowResponseImplCopyWith<_$DeleteFollowResponseImpl> 174 + get copyWith => throw _privateConstructorUsedError; 175 + }
+15
lib/models/procedures/delete_follow_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_follow_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeleteFollowResponseImpl _$$DeleteFollowResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeleteFollowResponseImpl(success: json['success'] as bool); 12 + 13 + Map<String, dynamic> _$$DeleteFollowResponseImplToJson( 14 + _$DeleteFollowResponseImpl instance, 15 + ) => <String, dynamic>{'success': instance.success};
+14
lib/models/procedures/delete_photo_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_photo_request.freezed.dart'; 4 + part 'delete_photo_request.g.dart'; 5 + 6 + /// Request model for deleting a photo. 7 + /// See lexicon: social.grain.photo.deletePhoto 8 + @freezed 9 + class DeletePhotoRequest with _$DeletePhotoRequest { 10 + const factory DeletePhotoRequest({required String uri}) = _DeletePhotoRequest; 11 + 12 + factory DeletePhotoRequest.fromJson(Map<String, dynamic> json) => 13 + _$DeletePhotoRequestFromJson(json); 14 + }
+171
lib/models/procedures/delete_photo_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_photo_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeletePhotoRequest _$DeletePhotoRequestFromJson(Map<String, dynamic> json) { 19 + return _DeletePhotoRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$DeletePhotoRequest { 24 + String get uri => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this DeletePhotoRequest to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of DeletePhotoRequest 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $DeletePhotoRequestCopyWith<DeletePhotoRequest> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $DeletePhotoRequestCopyWith<$Res> { 38 + factory $DeletePhotoRequestCopyWith( 39 + DeletePhotoRequest value, 40 + $Res Function(DeletePhotoRequest) then, 41 + ) = _$DeletePhotoRequestCopyWithImpl<$Res, DeletePhotoRequest>; 42 + @useResult 43 + $Res call({String uri}); 44 + } 45 + 46 + /// @nodoc 47 + class _$DeletePhotoRequestCopyWithImpl<$Res, $Val extends DeletePhotoRequest> 48 + implements $DeletePhotoRequestCopyWith<$Res> { 49 + _$DeletePhotoRequestCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of DeletePhotoRequest 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? uri = null}) { 61 + return _then( 62 + _value.copyWith( 63 + uri: null == uri 64 + ? _value.uri 65 + : uri // ignore: cast_nullable_to_non_nullable 66 + as String, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$DeletePhotoRequestImplCopyWith<$Res> 75 + implements $DeletePhotoRequestCopyWith<$Res> { 76 + factory _$$DeletePhotoRequestImplCopyWith( 77 + _$DeletePhotoRequestImpl value, 78 + $Res Function(_$DeletePhotoRequestImpl) then, 79 + ) = __$$DeletePhotoRequestImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({String uri}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$DeletePhotoRequestImplCopyWithImpl<$Res> 87 + extends _$DeletePhotoRequestCopyWithImpl<$Res, _$DeletePhotoRequestImpl> 88 + implements _$$DeletePhotoRequestImplCopyWith<$Res> { 89 + __$$DeletePhotoRequestImplCopyWithImpl( 90 + _$DeletePhotoRequestImpl _value, 91 + $Res Function(_$DeletePhotoRequestImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of DeletePhotoRequest 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? uri = null}) { 99 + return _then( 100 + _$DeletePhotoRequestImpl( 101 + uri: null == uri 102 + ? _value.uri 103 + : uri // ignore: cast_nullable_to_non_nullable 104 + as String, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$DeletePhotoRequestImpl implements _DeletePhotoRequest { 113 + const _$DeletePhotoRequestImpl({required this.uri}); 114 + 115 + factory _$DeletePhotoRequestImpl.fromJson(Map<String, dynamic> json) => 116 + _$$DeletePhotoRequestImplFromJson(json); 117 + 118 + @override 119 + final String uri; 120 + 121 + @override 122 + String toString() { 123 + return 'DeletePhotoRequest(uri: $uri)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$DeletePhotoRequestImpl && 131 + (identical(other.uri, uri) || other.uri == uri)); 132 + } 133 + 134 + @JsonKey(includeFromJson: false, includeToJson: false) 135 + @override 136 + int get hashCode => Object.hash(runtimeType, uri); 137 + 138 + /// Create a copy of DeletePhotoRequest 139 + /// with the given fields replaced by the non-null parameter values. 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + @pragma('vm:prefer-inline') 143 + _$$DeletePhotoRequestImplCopyWith<_$DeletePhotoRequestImpl> get copyWith => 144 + __$$DeletePhotoRequestImplCopyWithImpl<_$DeletePhotoRequestImpl>( 145 + this, 146 + _$identity, 147 + ); 148 + 149 + @override 150 + Map<String, dynamic> toJson() { 151 + return _$$DeletePhotoRequestImplToJson(this); 152 + } 153 + } 154 + 155 + abstract class _DeletePhotoRequest implements DeletePhotoRequest { 156 + const factory _DeletePhotoRequest({required final String uri}) = 157 + _$DeletePhotoRequestImpl; 158 + 159 + factory _DeletePhotoRequest.fromJson(Map<String, dynamic> json) = 160 + _$DeletePhotoRequestImpl.fromJson; 161 + 162 + @override 163 + String get uri; 164 + 165 + /// Create a copy of DeletePhotoRequest 166 + /// with the given fields replaced by the non-null parameter values. 167 + @override 168 + @JsonKey(includeFromJson: false, includeToJson: false) 169 + _$$DeletePhotoRequestImplCopyWith<_$DeletePhotoRequestImpl> get copyWith => 170 + throw _privateConstructorUsedError; 171 + }
+15
lib/models/procedures/delete_photo_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_photo_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeletePhotoRequestImpl _$$DeletePhotoRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeletePhotoRequestImpl(uri: json['uri'] as String); 12 + 13 + Map<String, dynamic> _$$DeletePhotoRequestImplToJson( 14 + _$DeletePhotoRequestImpl instance, 15 + ) => <String, dynamic>{'uri': instance.uri};
+14
lib/models/procedures/delete_photo_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'delete_photo_response.freezed.dart'; 4 + part 'delete_photo_response.g.dart'; 5 + 6 + /// Response model for deleting a photo. 7 + /// See lexicon: social.grain.photo.deletePhoto 8 + @freezed 9 + class DeletePhotoResponse with _$DeletePhotoResponse { 10 + const factory DeletePhotoResponse({required bool success}) = _DeletePhotoResponse; 11 + 12 + factory DeletePhotoResponse.fromJson(Map<String, dynamic> json) => 13 + _$DeletePhotoResponseFromJson(json); 14 + }
+171
lib/models/procedures/delete_photo_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'delete_photo_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + DeletePhotoResponse _$DeletePhotoResponseFromJson(Map<String, dynamic> json) { 19 + return _DeletePhotoResponse.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$DeletePhotoResponse { 24 + bool get success => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this DeletePhotoResponse to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of DeletePhotoResponse 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $DeletePhotoResponseCopyWith<DeletePhotoResponse> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $DeletePhotoResponseCopyWith<$Res> { 38 + factory $DeletePhotoResponseCopyWith( 39 + DeletePhotoResponse value, 40 + $Res Function(DeletePhotoResponse) then, 41 + ) = _$DeletePhotoResponseCopyWithImpl<$Res, DeletePhotoResponse>; 42 + @useResult 43 + $Res call({bool success}); 44 + } 45 + 46 + /// @nodoc 47 + class _$DeletePhotoResponseCopyWithImpl<$Res, $Val extends DeletePhotoResponse> 48 + implements $DeletePhotoResponseCopyWith<$Res> { 49 + _$DeletePhotoResponseCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of DeletePhotoResponse 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? success = null}) { 61 + return _then( 62 + _value.copyWith( 63 + success: null == success 64 + ? _value.success 65 + : success // ignore: cast_nullable_to_non_nullable 66 + as bool, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$DeletePhotoResponseImplCopyWith<$Res> 75 + implements $DeletePhotoResponseCopyWith<$Res> { 76 + factory _$$DeletePhotoResponseImplCopyWith( 77 + _$DeletePhotoResponseImpl value, 78 + $Res Function(_$DeletePhotoResponseImpl) then, 79 + ) = __$$DeletePhotoResponseImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({bool success}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$DeletePhotoResponseImplCopyWithImpl<$Res> 87 + extends _$DeletePhotoResponseCopyWithImpl<$Res, _$DeletePhotoResponseImpl> 88 + implements _$$DeletePhotoResponseImplCopyWith<$Res> { 89 + __$$DeletePhotoResponseImplCopyWithImpl( 90 + _$DeletePhotoResponseImpl _value, 91 + $Res Function(_$DeletePhotoResponseImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of DeletePhotoResponse 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? success = null}) { 99 + return _then( 100 + _$DeletePhotoResponseImpl( 101 + success: null == success 102 + ? _value.success 103 + : success // ignore: cast_nullable_to_non_nullable 104 + as bool, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$DeletePhotoResponseImpl implements _DeletePhotoResponse { 113 + const _$DeletePhotoResponseImpl({required this.success}); 114 + 115 + factory _$DeletePhotoResponseImpl.fromJson(Map<String, dynamic> json) => 116 + _$$DeletePhotoResponseImplFromJson(json); 117 + 118 + @override 119 + final bool success; 120 + 121 + @override 122 + String toString() { 123 + return 'DeletePhotoResponse(success: $success)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$DeletePhotoResponseImpl && 131 + (identical(other.success, success) || other.success == success)); 132 + } 133 + 134 + @JsonKey(includeFromJson: false, includeToJson: false) 135 + @override 136 + int get hashCode => Object.hash(runtimeType, success); 137 + 138 + /// Create a copy of DeletePhotoResponse 139 + /// with the given fields replaced by the non-null parameter values. 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + @pragma('vm:prefer-inline') 143 + _$$DeletePhotoResponseImplCopyWith<_$DeletePhotoResponseImpl> get copyWith => 144 + __$$DeletePhotoResponseImplCopyWithImpl<_$DeletePhotoResponseImpl>( 145 + this, 146 + _$identity, 147 + ); 148 + 149 + @override 150 + Map<String, dynamic> toJson() { 151 + return _$$DeletePhotoResponseImplToJson(this); 152 + } 153 + } 154 + 155 + abstract class _DeletePhotoResponse implements DeletePhotoResponse { 156 + const factory _DeletePhotoResponse({required final bool success}) = 157 + _$DeletePhotoResponseImpl; 158 + 159 + factory _DeletePhotoResponse.fromJson(Map<String, dynamic> json) = 160 + _$DeletePhotoResponseImpl.fromJson; 161 + 162 + @override 163 + bool get success; 164 + 165 + /// Create a copy of DeletePhotoResponse 166 + /// with the given fields replaced by the non-null parameter values. 167 + @override 168 + @JsonKey(includeFromJson: false, includeToJson: false) 169 + _$$DeletePhotoResponseImplCopyWith<_$DeletePhotoResponseImpl> get copyWith => 170 + throw _privateConstructorUsedError; 171 + }
+15
lib/models/procedures/delete_photo_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'delete_photo_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$DeletePhotoResponseImpl _$$DeletePhotoResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$DeletePhotoResponseImpl(success: json['success'] as bool); 12 + 13 + Map<String, dynamic> _$$DeletePhotoResponseImplToJson( 14 + _$DeletePhotoResponseImpl instance, 15 + ) => <String, dynamic>{'success': instance.success};
+34
lib/models/procedures/procedures.dart
···
··· 1 + export 'apply_alts_request.dart'; 2 + export 'apply_alts_response.dart'; 3 + export 'apply_sort_request.dart'; 4 + export 'apply_sort_response.dart'; 5 + export 'create_comment_request.dart'; 6 + export 'create_comment_response.dart'; 7 + export 'create_exif_request.dart'; 8 + export 'create_exif_response.dart'; 9 + export 'create_favorite_request.dart'; 10 + export 'create_favorite_response.dart'; 11 + export 'create_follow_request.dart'; 12 + export 'create_follow_response.dart'; 13 + export 'create_gallery_item_request.dart'; 14 + export 'create_gallery_item_response.dart'; 15 + export 'create_gallery_request.dart'; 16 + export 'create_gallery_response.dart'; 17 + export 'delete_comment_request.dart'; 18 + export 'delete_comment_response.dart'; 19 + export 'delete_favorite_request.dart'; 20 + export 'delete_favorite_response.dart'; 21 + export 'delete_follow_request.dart'; 22 + export 'delete_follow_response.dart'; 23 + export 'delete_gallery_item_request.dart'; 24 + export 'delete_gallery_item_response.dart'; 25 + export 'delete_gallery_request.dart'; 26 + export 'delete_gallery_response.dart'; 27 + export 'delete_photo_request.dart'; 28 + export 'delete_photo_response.dart'; 29 + export 'update_avatar_response.dart'; 30 + export 'update_gallery_request.dart'; 31 + export 'update_gallery_response.dart'; 32 + export 'update_profile_request.dart'; 33 + export 'update_profile_response.dart'; 34 + export 'upload_photo_response.dart';
+15
lib/models/procedures/update_avatar_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'update_avatar_response.freezed.dart'; 4 + part 'update_avatar_response.g.dart'; 5 + 6 + /// Response model for updating an actor's avatar image. 7 + /// 8 + /// [success] - Indicates if the update was successful. 9 + @freezed 10 + class UpdateAvatarResponse with _$UpdateAvatarResponse { 11 + const factory UpdateAvatarResponse({required bool success}) = _UpdateAvatarResponse; 12 + 13 + factory UpdateAvatarResponse.fromJson(Map<String, dynamic> json) => 14 + _$UpdateAvatarResponseFromJson(json); 15 + }
+175
lib/models/procedures/update_avatar_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'update_avatar_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + UpdateAvatarResponse _$UpdateAvatarResponseFromJson(Map<String, dynamic> json) { 19 + return _UpdateAvatarResponse.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$UpdateAvatarResponse { 24 + bool get success => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this UpdateAvatarResponse to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of UpdateAvatarResponse 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $UpdateAvatarResponseCopyWith<UpdateAvatarResponse> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $UpdateAvatarResponseCopyWith<$Res> { 38 + factory $UpdateAvatarResponseCopyWith( 39 + UpdateAvatarResponse value, 40 + $Res Function(UpdateAvatarResponse) then, 41 + ) = _$UpdateAvatarResponseCopyWithImpl<$Res, UpdateAvatarResponse>; 42 + @useResult 43 + $Res call({bool success}); 44 + } 45 + 46 + /// @nodoc 47 + class _$UpdateAvatarResponseCopyWithImpl< 48 + $Res, 49 + $Val extends UpdateAvatarResponse 50 + > 51 + implements $UpdateAvatarResponseCopyWith<$Res> { 52 + _$UpdateAvatarResponseCopyWithImpl(this._value, this._then); 53 + 54 + // ignore: unused_field 55 + final $Val _value; 56 + // ignore: unused_field 57 + final $Res Function($Val) _then; 58 + 59 + /// Create a copy of UpdateAvatarResponse 60 + /// with the given fields replaced by the non-null parameter values. 61 + @pragma('vm:prefer-inline') 62 + @override 63 + $Res call({Object? success = null}) { 64 + return _then( 65 + _value.copyWith( 66 + success: null == success 67 + ? _value.success 68 + : success // ignore: cast_nullable_to_non_nullable 69 + as bool, 70 + ) 71 + as $Val, 72 + ); 73 + } 74 + } 75 + 76 + /// @nodoc 77 + abstract class _$$UpdateAvatarResponseImplCopyWith<$Res> 78 + implements $UpdateAvatarResponseCopyWith<$Res> { 79 + factory _$$UpdateAvatarResponseImplCopyWith( 80 + _$UpdateAvatarResponseImpl value, 81 + $Res Function(_$UpdateAvatarResponseImpl) then, 82 + ) = __$$UpdateAvatarResponseImplCopyWithImpl<$Res>; 83 + @override 84 + @useResult 85 + $Res call({bool success}); 86 + } 87 + 88 + /// @nodoc 89 + class __$$UpdateAvatarResponseImplCopyWithImpl<$Res> 90 + extends _$UpdateAvatarResponseCopyWithImpl<$Res, _$UpdateAvatarResponseImpl> 91 + implements _$$UpdateAvatarResponseImplCopyWith<$Res> { 92 + __$$UpdateAvatarResponseImplCopyWithImpl( 93 + _$UpdateAvatarResponseImpl _value, 94 + $Res Function(_$UpdateAvatarResponseImpl) _then, 95 + ) : super(_value, _then); 96 + 97 + /// Create a copy of UpdateAvatarResponse 98 + /// with the given fields replaced by the non-null parameter values. 99 + @pragma('vm:prefer-inline') 100 + @override 101 + $Res call({Object? success = null}) { 102 + return _then( 103 + _$UpdateAvatarResponseImpl( 104 + success: null == success 105 + ? _value.success 106 + : success // ignore: cast_nullable_to_non_nullable 107 + as bool, 108 + ), 109 + ); 110 + } 111 + } 112 + 113 + /// @nodoc 114 + @JsonSerializable() 115 + class _$UpdateAvatarResponseImpl implements _UpdateAvatarResponse { 116 + const _$UpdateAvatarResponseImpl({required this.success}); 117 + 118 + factory _$UpdateAvatarResponseImpl.fromJson(Map<String, dynamic> json) => 119 + _$$UpdateAvatarResponseImplFromJson(json); 120 + 121 + @override 122 + final bool success; 123 + 124 + @override 125 + String toString() { 126 + return 'UpdateAvatarResponse(success: $success)'; 127 + } 128 + 129 + @override 130 + bool operator ==(Object other) { 131 + return identical(this, other) || 132 + (other.runtimeType == runtimeType && 133 + other is _$UpdateAvatarResponseImpl && 134 + (identical(other.success, success) || other.success == success)); 135 + } 136 + 137 + @JsonKey(includeFromJson: false, includeToJson: false) 138 + @override 139 + int get hashCode => Object.hash(runtimeType, success); 140 + 141 + /// Create a copy of UpdateAvatarResponse 142 + /// with the given fields replaced by the non-null parameter values. 143 + @JsonKey(includeFromJson: false, includeToJson: false) 144 + @override 145 + @pragma('vm:prefer-inline') 146 + _$$UpdateAvatarResponseImplCopyWith<_$UpdateAvatarResponseImpl> 147 + get copyWith => 148 + __$$UpdateAvatarResponseImplCopyWithImpl<_$UpdateAvatarResponseImpl>( 149 + this, 150 + _$identity, 151 + ); 152 + 153 + @override 154 + Map<String, dynamic> toJson() { 155 + return _$$UpdateAvatarResponseImplToJson(this); 156 + } 157 + } 158 + 159 + abstract class _UpdateAvatarResponse implements UpdateAvatarResponse { 160 + const factory _UpdateAvatarResponse({required final bool success}) = 161 + _$UpdateAvatarResponseImpl; 162 + 163 + factory _UpdateAvatarResponse.fromJson(Map<String, dynamic> json) = 164 + _$UpdateAvatarResponseImpl.fromJson; 165 + 166 + @override 167 + bool get success; 168 + 169 + /// Create a copy of UpdateAvatarResponse 170 + /// with the given fields replaced by the non-null parameter values. 171 + @override 172 + @JsonKey(includeFromJson: false, includeToJson: false) 173 + _$$UpdateAvatarResponseImplCopyWith<_$UpdateAvatarResponseImpl> 174 + get copyWith => throw _privateConstructorUsedError; 175 + }
+15
lib/models/procedures/update_avatar_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'update_avatar_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$UpdateAvatarResponseImpl _$$UpdateAvatarResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$UpdateAvatarResponseImpl(success: json['success'] as bool); 12 + 13 + Map<String, dynamic> _$$UpdateAvatarResponseImplToJson( 14 + _$UpdateAvatarResponseImpl instance, 15 + ) => <String, dynamic>{'success': instance.success};
+17
lib/models/procedures/update_profile_request.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'update_profile_request.freezed.dart'; 4 + part 'update_profile_request.g.dart'; 5 + 6 + /// Request model for updating an actor's profile information. 7 + /// 8 + /// [displayName] - The display name (optional). 9 + /// [description] - The profile description (optional). 10 + @freezed 11 + class UpdateProfileRequest with _$UpdateProfileRequest { 12 + const factory UpdateProfileRequest({String? displayName, String? description}) = 13 + _UpdateProfileRequest; 14 + 15 + factory UpdateProfileRequest.fromJson(Map<String, dynamic> json) => 16 + _$UpdateProfileRequestFromJson(json); 17 + }
+193
lib/models/procedures/update_profile_request.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'update_profile_request.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + UpdateProfileRequest _$UpdateProfileRequestFromJson(Map<String, dynamic> json) { 19 + return _UpdateProfileRequest.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$UpdateProfileRequest { 24 + String? get displayName => throw _privateConstructorUsedError; 25 + String? get description => throw _privateConstructorUsedError; 26 + 27 + /// Serializes this UpdateProfileRequest to a JSON map. 28 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 29 + 30 + /// Create a copy of UpdateProfileRequest 31 + /// with the given fields replaced by the non-null parameter values. 32 + @JsonKey(includeFromJson: false, includeToJson: false) 33 + $UpdateProfileRequestCopyWith<UpdateProfileRequest> get copyWith => 34 + throw _privateConstructorUsedError; 35 + } 36 + 37 + /// @nodoc 38 + abstract class $UpdateProfileRequestCopyWith<$Res> { 39 + factory $UpdateProfileRequestCopyWith( 40 + UpdateProfileRequest value, 41 + $Res Function(UpdateProfileRequest) then, 42 + ) = _$UpdateProfileRequestCopyWithImpl<$Res, UpdateProfileRequest>; 43 + @useResult 44 + $Res call({String? displayName, String? description}); 45 + } 46 + 47 + /// @nodoc 48 + class _$UpdateProfileRequestCopyWithImpl< 49 + $Res, 50 + $Val extends UpdateProfileRequest 51 + > 52 + implements $UpdateProfileRequestCopyWith<$Res> { 53 + _$UpdateProfileRequestCopyWithImpl(this._value, this._then); 54 + 55 + // ignore: unused_field 56 + final $Val _value; 57 + // ignore: unused_field 58 + final $Res Function($Val) _then; 59 + 60 + /// Create a copy of UpdateProfileRequest 61 + /// with the given fields replaced by the non-null parameter values. 62 + @pragma('vm:prefer-inline') 63 + @override 64 + $Res call({Object? displayName = freezed, Object? description = freezed}) { 65 + return _then( 66 + _value.copyWith( 67 + displayName: freezed == displayName 68 + ? _value.displayName 69 + : displayName // ignore: cast_nullable_to_non_nullable 70 + as String?, 71 + description: freezed == description 72 + ? _value.description 73 + : description // ignore: cast_nullable_to_non_nullable 74 + as String?, 75 + ) 76 + as $Val, 77 + ); 78 + } 79 + } 80 + 81 + /// @nodoc 82 + abstract class _$$UpdateProfileRequestImplCopyWith<$Res> 83 + implements $UpdateProfileRequestCopyWith<$Res> { 84 + factory _$$UpdateProfileRequestImplCopyWith( 85 + _$UpdateProfileRequestImpl value, 86 + $Res Function(_$UpdateProfileRequestImpl) then, 87 + ) = __$$UpdateProfileRequestImplCopyWithImpl<$Res>; 88 + @override 89 + @useResult 90 + $Res call({String? displayName, String? description}); 91 + } 92 + 93 + /// @nodoc 94 + class __$$UpdateProfileRequestImplCopyWithImpl<$Res> 95 + extends _$UpdateProfileRequestCopyWithImpl<$Res, _$UpdateProfileRequestImpl> 96 + implements _$$UpdateProfileRequestImplCopyWith<$Res> { 97 + __$$UpdateProfileRequestImplCopyWithImpl( 98 + _$UpdateProfileRequestImpl _value, 99 + $Res Function(_$UpdateProfileRequestImpl) _then, 100 + ) : super(_value, _then); 101 + 102 + /// Create a copy of UpdateProfileRequest 103 + /// with the given fields replaced by the non-null parameter values. 104 + @pragma('vm:prefer-inline') 105 + @override 106 + $Res call({Object? displayName = freezed, Object? description = freezed}) { 107 + return _then( 108 + _$UpdateProfileRequestImpl( 109 + displayName: freezed == displayName 110 + ? _value.displayName 111 + : displayName // ignore: cast_nullable_to_non_nullable 112 + as String?, 113 + description: freezed == description 114 + ? _value.description 115 + : description // ignore: cast_nullable_to_non_nullable 116 + as String?, 117 + ), 118 + ); 119 + } 120 + } 121 + 122 + /// @nodoc 123 + @JsonSerializable() 124 + class _$UpdateProfileRequestImpl implements _UpdateProfileRequest { 125 + const _$UpdateProfileRequestImpl({this.displayName, this.description}); 126 + 127 + factory _$UpdateProfileRequestImpl.fromJson(Map<String, dynamic> json) => 128 + _$$UpdateProfileRequestImplFromJson(json); 129 + 130 + @override 131 + final String? displayName; 132 + @override 133 + final String? description; 134 + 135 + @override 136 + String toString() { 137 + return 'UpdateProfileRequest(displayName: $displayName, description: $description)'; 138 + } 139 + 140 + @override 141 + bool operator ==(Object other) { 142 + return identical(this, other) || 143 + (other.runtimeType == runtimeType && 144 + other is _$UpdateProfileRequestImpl && 145 + (identical(other.displayName, displayName) || 146 + other.displayName == displayName) && 147 + (identical(other.description, description) || 148 + other.description == description)); 149 + } 150 + 151 + @JsonKey(includeFromJson: false, includeToJson: false) 152 + @override 153 + int get hashCode => Object.hash(runtimeType, displayName, description); 154 + 155 + /// Create a copy of UpdateProfileRequest 156 + /// with the given fields replaced by the non-null parameter values. 157 + @JsonKey(includeFromJson: false, includeToJson: false) 158 + @override 159 + @pragma('vm:prefer-inline') 160 + _$$UpdateProfileRequestImplCopyWith<_$UpdateProfileRequestImpl> 161 + get copyWith => 162 + __$$UpdateProfileRequestImplCopyWithImpl<_$UpdateProfileRequestImpl>( 163 + this, 164 + _$identity, 165 + ); 166 + 167 + @override 168 + Map<String, dynamic> toJson() { 169 + return _$$UpdateProfileRequestImplToJson(this); 170 + } 171 + } 172 + 173 + abstract class _UpdateProfileRequest implements UpdateProfileRequest { 174 + const factory _UpdateProfileRequest({ 175 + final String? displayName, 176 + final String? description, 177 + }) = _$UpdateProfileRequestImpl; 178 + 179 + factory _UpdateProfileRequest.fromJson(Map<String, dynamic> json) = 180 + _$UpdateProfileRequestImpl.fromJson; 181 + 182 + @override 183 + String? get displayName; 184 + @override 185 + String? get description; 186 + 187 + /// Create a copy of UpdateProfileRequest 188 + /// with the given fields replaced by the non-null parameter values. 189 + @override 190 + @JsonKey(includeFromJson: false, includeToJson: false) 191 + _$$UpdateProfileRequestImplCopyWith<_$UpdateProfileRequestImpl> 192 + get copyWith => throw _privateConstructorUsedError; 193 + }
+21
lib/models/procedures/update_profile_request.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'update_profile_request.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$UpdateProfileRequestImpl _$$UpdateProfileRequestImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$UpdateProfileRequestImpl( 12 + displayName: json['displayName'] as String?, 13 + description: json['description'] as String?, 14 + ); 15 + 16 + Map<String, dynamic> _$$UpdateProfileRequestImplToJson( 17 + _$UpdateProfileRequestImpl instance, 18 + ) => <String, dynamic>{ 19 + 'displayName': instance.displayName, 20 + 'description': instance.description, 21 + };
+15
lib/models/procedures/update_profile_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'update_profile_response.freezed.dart'; 4 + part 'update_profile_response.g.dart'; 5 + 6 + /// Response model for updating an actor's profile information. 7 + /// 8 + /// [success] - Indicates if the update was successful. 9 + @freezed 10 + class UpdateProfileResponse with _$UpdateProfileResponse { 11 + const factory UpdateProfileResponse({required bool success}) = _UpdateProfileResponse; 12 + 13 + factory UpdateProfileResponse.fromJson(Map<String, dynamic> json) => 14 + _$UpdateProfileResponseFromJson(json); 15 + }
+178
lib/models/procedures/update_profile_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'update_profile_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + UpdateProfileResponse _$UpdateProfileResponseFromJson( 19 + Map<String, dynamic> json, 20 + ) { 21 + return _UpdateProfileResponse.fromJson(json); 22 + } 23 + 24 + /// @nodoc 25 + mixin _$UpdateProfileResponse { 26 + bool get success => throw _privateConstructorUsedError; 27 + 28 + /// Serializes this UpdateProfileResponse to a JSON map. 29 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 30 + 31 + /// Create a copy of UpdateProfileResponse 32 + /// with the given fields replaced by the non-null parameter values. 33 + @JsonKey(includeFromJson: false, includeToJson: false) 34 + $UpdateProfileResponseCopyWith<UpdateProfileResponse> get copyWith => 35 + throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $UpdateProfileResponseCopyWith<$Res> { 40 + factory $UpdateProfileResponseCopyWith( 41 + UpdateProfileResponse value, 42 + $Res Function(UpdateProfileResponse) then, 43 + ) = _$UpdateProfileResponseCopyWithImpl<$Res, UpdateProfileResponse>; 44 + @useResult 45 + $Res call({bool success}); 46 + } 47 + 48 + /// @nodoc 49 + class _$UpdateProfileResponseCopyWithImpl< 50 + $Res, 51 + $Val extends UpdateProfileResponse 52 + > 53 + implements $UpdateProfileResponseCopyWith<$Res> { 54 + _$UpdateProfileResponseCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of UpdateProfileResponse 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({Object? success = null}) { 66 + return _then( 67 + _value.copyWith( 68 + success: null == success 69 + ? _value.success 70 + : success // ignore: cast_nullable_to_non_nullable 71 + as bool, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$UpdateProfileResponseImplCopyWith<$Res> 80 + implements $UpdateProfileResponseCopyWith<$Res> { 81 + factory _$$UpdateProfileResponseImplCopyWith( 82 + _$UpdateProfileResponseImpl value, 83 + $Res Function(_$UpdateProfileResponseImpl) then, 84 + ) = __$$UpdateProfileResponseImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({bool success}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$UpdateProfileResponseImplCopyWithImpl<$Res> 92 + extends 93 + _$UpdateProfileResponseCopyWithImpl<$Res, _$UpdateProfileResponseImpl> 94 + implements _$$UpdateProfileResponseImplCopyWith<$Res> { 95 + __$$UpdateProfileResponseImplCopyWithImpl( 96 + _$UpdateProfileResponseImpl _value, 97 + $Res Function(_$UpdateProfileResponseImpl) _then, 98 + ) : super(_value, _then); 99 + 100 + /// Create a copy of UpdateProfileResponse 101 + /// with the given fields replaced by the non-null parameter values. 102 + @pragma('vm:prefer-inline') 103 + @override 104 + $Res call({Object? success = null}) { 105 + return _then( 106 + _$UpdateProfileResponseImpl( 107 + success: null == success 108 + ? _value.success 109 + : success // ignore: cast_nullable_to_non_nullable 110 + as bool, 111 + ), 112 + ); 113 + } 114 + } 115 + 116 + /// @nodoc 117 + @JsonSerializable() 118 + class _$UpdateProfileResponseImpl implements _UpdateProfileResponse { 119 + const _$UpdateProfileResponseImpl({required this.success}); 120 + 121 + factory _$UpdateProfileResponseImpl.fromJson(Map<String, dynamic> json) => 122 + _$$UpdateProfileResponseImplFromJson(json); 123 + 124 + @override 125 + final bool success; 126 + 127 + @override 128 + String toString() { 129 + return 'UpdateProfileResponse(success: $success)'; 130 + } 131 + 132 + @override 133 + bool operator ==(Object other) { 134 + return identical(this, other) || 135 + (other.runtimeType == runtimeType && 136 + other is _$UpdateProfileResponseImpl && 137 + (identical(other.success, success) || other.success == success)); 138 + } 139 + 140 + @JsonKey(includeFromJson: false, includeToJson: false) 141 + @override 142 + int get hashCode => Object.hash(runtimeType, success); 143 + 144 + /// Create a copy of UpdateProfileResponse 145 + /// with the given fields replaced by the non-null parameter values. 146 + @JsonKey(includeFromJson: false, includeToJson: false) 147 + @override 148 + @pragma('vm:prefer-inline') 149 + _$$UpdateProfileResponseImplCopyWith<_$UpdateProfileResponseImpl> 150 + get copyWith => 151 + __$$UpdateProfileResponseImplCopyWithImpl<_$UpdateProfileResponseImpl>( 152 + this, 153 + _$identity, 154 + ); 155 + 156 + @override 157 + Map<String, dynamic> toJson() { 158 + return _$$UpdateProfileResponseImplToJson(this); 159 + } 160 + } 161 + 162 + abstract class _UpdateProfileResponse implements UpdateProfileResponse { 163 + const factory _UpdateProfileResponse({required final bool success}) = 164 + _$UpdateProfileResponseImpl; 165 + 166 + factory _UpdateProfileResponse.fromJson(Map<String, dynamic> json) = 167 + _$UpdateProfileResponseImpl.fromJson; 168 + 169 + @override 170 + bool get success; 171 + 172 + /// Create a copy of UpdateProfileResponse 173 + /// with the given fields replaced by the non-null parameter values. 174 + @override 175 + @JsonKey(includeFromJson: false, includeToJson: false) 176 + _$$UpdateProfileResponseImplCopyWith<_$UpdateProfileResponseImpl> 177 + get copyWith => throw _privateConstructorUsedError; 178 + }
+15
lib/models/procedures/update_profile_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'update_profile_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$UpdateProfileResponseImpl _$$UpdateProfileResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$UpdateProfileResponseImpl(success: json['success'] as bool); 12 + 13 + Map<String, dynamic> _$$UpdateProfileResponseImplToJson( 14 + _$UpdateProfileResponseImpl instance, 15 + ) => <String, dynamic>{'success': instance.success};
+14
lib/models/procedures/upload_photo_response.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'upload_photo_response.freezed.dart'; 4 + part 'upload_photo_response.g.dart'; 5 + 6 + /// Response model for uploading a photo. 7 + /// See lexicon: social.grain.photo.uploadPhoto 8 + @freezed 9 + class UploadPhotoResponse with _$UploadPhotoResponse { 10 + const factory UploadPhotoResponse({required String photoUri}) = _UploadPhotoResponse; 11 + 12 + factory UploadPhotoResponse.fromJson(Map<String, dynamic> json) => 13 + _$UploadPhotoResponseFromJson(json); 14 + }
+172
lib/models/procedures/upload_photo_response.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'upload_photo_response.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + UploadPhotoResponse _$UploadPhotoResponseFromJson(Map<String, dynamic> json) { 19 + return _UploadPhotoResponse.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$UploadPhotoResponse { 24 + String get photoUri => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this UploadPhotoResponse to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of UploadPhotoResponse 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $UploadPhotoResponseCopyWith<UploadPhotoResponse> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $UploadPhotoResponseCopyWith<$Res> { 38 + factory $UploadPhotoResponseCopyWith( 39 + UploadPhotoResponse value, 40 + $Res Function(UploadPhotoResponse) then, 41 + ) = _$UploadPhotoResponseCopyWithImpl<$Res, UploadPhotoResponse>; 42 + @useResult 43 + $Res call({String photoUri}); 44 + } 45 + 46 + /// @nodoc 47 + class _$UploadPhotoResponseCopyWithImpl<$Res, $Val extends UploadPhotoResponse> 48 + implements $UploadPhotoResponseCopyWith<$Res> { 49 + _$UploadPhotoResponseCopyWithImpl(this._value, this._then); 50 + 51 + // ignore: unused_field 52 + final $Val _value; 53 + // ignore: unused_field 54 + final $Res Function($Val) _then; 55 + 56 + /// Create a copy of UploadPhotoResponse 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? photoUri = null}) { 61 + return _then( 62 + _value.copyWith( 63 + photoUri: null == photoUri 64 + ? _value.photoUri 65 + : photoUri // ignore: cast_nullable_to_non_nullable 66 + as String, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$UploadPhotoResponseImplCopyWith<$Res> 75 + implements $UploadPhotoResponseCopyWith<$Res> { 76 + factory _$$UploadPhotoResponseImplCopyWith( 77 + _$UploadPhotoResponseImpl value, 78 + $Res Function(_$UploadPhotoResponseImpl) then, 79 + ) = __$$UploadPhotoResponseImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({String photoUri}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$UploadPhotoResponseImplCopyWithImpl<$Res> 87 + extends _$UploadPhotoResponseCopyWithImpl<$Res, _$UploadPhotoResponseImpl> 88 + implements _$$UploadPhotoResponseImplCopyWith<$Res> { 89 + __$$UploadPhotoResponseImplCopyWithImpl( 90 + _$UploadPhotoResponseImpl _value, 91 + $Res Function(_$UploadPhotoResponseImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of UploadPhotoResponse 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? photoUri = null}) { 99 + return _then( 100 + _$UploadPhotoResponseImpl( 101 + photoUri: null == photoUri 102 + ? _value.photoUri 103 + : photoUri // ignore: cast_nullable_to_non_nullable 104 + as String, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$UploadPhotoResponseImpl implements _UploadPhotoResponse { 113 + const _$UploadPhotoResponseImpl({required this.photoUri}); 114 + 115 + factory _$UploadPhotoResponseImpl.fromJson(Map<String, dynamic> json) => 116 + _$$UploadPhotoResponseImplFromJson(json); 117 + 118 + @override 119 + final String photoUri; 120 + 121 + @override 122 + String toString() { 123 + return 'UploadPhotoResponse(photoUri: $photoUri)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$UploadPhotoResponseImpl && 131 + (identical(other.photoUri, photoUri) || 132 + other.photoUri == photoUri)); 133 + } 134 + 135 + @JsonKey(includeFromJson: false, includeToJson: false) 136 + @override 137 + int get hashCode => Object.hash(runtimeType, photoUri); 138 + 139 + /// Create a copy of UploadPhotoResponse 140 + /// with the given fields replaced by the non-null parameter values. 141 + @JsonKey(includeFromJson: false, includeToJson: false) 142 + @override 143 + @pragma('vm:prefer-inline') 144 + _$$UploadPhotoResponseImplCopyWith<_$UploadPhotoResponseImpl> get copyWith => 145 + __$$UploadPhotoResponseImplCopyWithImpl<_$UploadPhotoResponseImpl>( 146 + this, 147 + _$identity, 148 + ); 149 + 150 + @override 151 + Map<String, dynamic> toJson() { 152 + return _$$UploadPhotoResponseImplToJson(this); 153 + } 154 + } 155 + 156 + abstract class _UploadPhotoResponse implements UploadPhotoResponse { 157 + const factory _UploadPhotoResponse({required final String photoUri}) = 158 + _$UploadPhotoResponseImpl; 159 + 160 + factory _UploadPhotoResponse.fromJson(Map<String, dynamic> json) = 161 + _$UploadPhotoResponseImpl.fromJson; 162 + 163 + @override 164 + String get photoUri; 165 + 166 + /// Create a copy of UploadPhotoResponse 167 + /// with the given fields replaced by the non-null parameter values. 168 + @override 169 + @JsonKey(includeFromJson: false, includeToJson: false) 170 + _$$UploadPhotoResponseImplCopyWith<_$UploadPhotoResponseImpl> get copyWith => 171 + throw _privateConstructorUsedError; 172 + }
+15
lib/models/procedures/upload_photo_response.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'upload_photo_response.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$UploadPhotoResponseImpl _$$UploadPhotoResponseImplFromJson( 10 + Map<String, dynamic> json, 11 + ) => _$UploadPhotoResponseImpl(photoUri: json['photoUri'] as String); 12 + 13 + Map<String, dynamic> _$$UploadPhotoResponseImplToJson( 14 + _$UploadPhotoResponseImpl instance, 15 + ) => <String, dynamic>{'photoUri': instance.photoUri};
+16
lib/models/session.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'session.freezed.dart'; 4 + part 'session.g.dart'; 5 + 6 + @freezed 7 + class Session with _$Session { 8 + const factory Session({ 9 + required String token, 10 + required String refreshToken, 11 + required DateTime expiresAt, 12 + required String did, 13 + }) = _Session; 14 + 15 + factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json); 16 + }
+237
lib/models/session.freezed.dart
···
··· 1 + // coverage:ignore-file 2 + // GENERATED CODE - DO NOT MODIFY BY HAND 3 + // ignore_for_file: type=lint 4 + // 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 5 + 6 + part of 'session.dart'; 7 + 8 + // ************************************************************************** 9 + // FreezedGenerator 10 + // ************************************************************************** 11 + 12 + T _$identity<T>(T value) => value; 13 + 14 + final _privateConstructorUsedError = UnsupportedError( 15 + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', 16 + ); 17 + 18 + Session _$SessionFromJson(Map<String, dynamic> json) { 19 + return _Session.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$Session { 24 + String get token => throw _privateConstructorUsedError; 25 + String get refreshToken => throw _privateConstructorUsedError; 26 + DateTime get expiresAt => throw _privateConstructorUsedError; 27 + String get did => throw _privateConstructorUsedError; 28 + 29 + /// Serializes this Session to a JSON map. 30 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 31 + 32 + /// Create a copy of Session 33 + /// with the given fields replaced by the non-null parameter values. 34 + @JsonKey(includeFromJson: false, includeToJson: false) 35 + $SessionCopyWith<Session> get copyWith => throw _privateConstructorUsedError; 36 + } 37 + 38 + /// @nodoc 39 + abstract class $SessionCopyWith<$Res> { 40 + factory $SessionCopyWith(Session value, $Res Function(Session) then) = 41 + _$SessionCopyWithImpl<$Res, Session>; 42 + @useResult 43 + $Res call({ 44 + String token, 45 + String refreshToken, 46 + DateTime expiresAt, 47 + String did, 48 + }); 49 + } 50 + 51 + /// @nodoc 52 + class _$SessionCopyWithImpl<$Res, $Val extends Session> 53 + implements $SessionCopyWith<$Res> { 54 + _$SessionCopyWithImpl(this._value, this._then); 55 + 56 + // ignore: unused_field 57 + final $Val _value; 58 + // ignore: unused_field 59 + final $Res Function($Val) _then; 60 + 61 + /// Create a copy of Session 62 + /// with the given fields replaced by the non-null parameter values. 63 + @pragma('vm:prefer-inline') 64 + @override 65 + $Res call({ 66 + Object? token = null, 67 + Object? refreshToken = null, 68 + Object? expiresAt = null, 69 + Object? did = null, 70 + }) { 71 + return _then( 72 + _value.copyWith( 73 + token: null == token 74 + ? _value.token 75 + : token // ignore: cast_nullable_to_non_nullable 76 + as String, 77 + refreshToken: null == refreshToken 78 + ? _value.refreshToken 79 + : refreshToken // ignore: cast_nullable_to_non_nullable 80 + as String, 81 + expiresAt: null == expiresAt 82 + ? _value.expiresAt 83 + : expiresAt // ignore: cast_nullable_to_non_nullable 84 + as DateTime, 85 + did: null == did 86 + ? _value.did 87 + : did // ignore: cast_nullable_to_non_nullable 88 + as String, 89 + ) 90 + as $Val, 91 + ); 92 + } 93 + } 94 + 95 + /// @nodoc 96 + abstract class _$$SessionImplCopyWith<$Res> implements $SessionCopyWith<$Res> { 97 + factory _$$SessionImplCopyWith( 98 + _$SessionImpl value, 99 + $Res Function(_$SessionImpl) then, 100 + ) = __$$SessionImplCopyWithImpl<$Res>; 101 + @override 102 + @useResult 103 + $Res call({ 104 + String token, 105 + String refreshToken, 106 + DateTime expiresAt, 107 + String did, 108 + }); 109 + } 110 + 111 + /// @nodoc 112 + class __$$SessionImplCopyWithImpl<$Res> 113 + extends _$SessionCopyWithImpl<$Res, _$SessionImpl> 114 + implements _$$SessionImplCopyWith<$Res> { 115 + __$$SessionImplCopyWithImpl( 116 + _$SessionImpl _value, 117 + $Res Function(_$SessionImpl) _then, 118 + ) : super(_value, _then); 119 + 120 + /// Create a copy of Session 121 + /// with the given fields replaced by the non-null parameter values. 122 + @pragma('vm:prefer-inline') 123 + @override 124 + $Res call({ 125 + Object? token = null, 126 + Object? refreshToken = null, 127 + Object? expiresAt = null, 128 + Object? did = null, 129 + }) { 130 + return _then( 131 + _$SessionImpl( 132 + token: null == token 133 + ? _value.token 134 + : token // ignore: cast_nullable_to_non_nullable 135 + as String, 136 + refreshToken: null == refreshToken 137 + ? _value.refreshToken 138 + : refreshToken // ignore: cast_nullable_to_non_nullable 139 + as String, 140 + expiresAt: null == expiresAt 141 + ? _value.expiresAt 142 + : expiresAt // ignore: cast_nullable_to_non_nullable 143 + as DateTime, 144 + did: null == did 145 + ? _value.did 146 + : did // ignore: cast_nullable_to_non_nullable 147 + as String, 148 + ), 149 + ); 150 + } 151 + } 152 + 153 + /// @nodoc 154 + @JsonSerializable() 155 + class _$SessionImpl implements _Session { 156 + const _$SessionImpl({ 157 + required this.token, 158 + required this.refreshToken, 159 + required this.expiresAt, 160 + required this.did, 161 + }); 162 + 163 + factory _$SessionImpl.fromJson(Map<String, dynamic> json) => 164 + _$$SessionImplFromJson(json); 165 + 166 + @override 167 + final String token; 168 + @override 169 + final String refreshToken; 170 + @override 171 + final DateTime expiresAt; 172 + @override 173 + final String did; 174 + 175 + @override 176 + String toString() { 177 + return 'Session(token: $token, refreshToken: $refreshToken, expiresAt: $expiresAt, did: $did)'; 178 + } 179 + 180 + @override 181 + bool operator ==(Object other) { 182 + return identical(this, other) || 183 + (other.runtimeType == runtimeType && 184 + other is _$SessionImpl && 185 + (identical(other.token, token) || other.token == token) && 186 + (identical(other.refreshToken, refreshToken) || 187 + other.refreshToken == refreshToken) && 188 + (identical(other.expiresAt, expiresAt) || 189 + other.expiresAt == expiresAt) && 190 + (identical(other.did, did) || other.did == did)); 191 + } 192 + 193 + @JsonKey(includeFromJson: false, includeToJson: false) 194 + @override 195 + int get hashCode => 196 + Object.hash(runtimeType, token, refreshToken, expiresAt, did); 197 + 198 + /// Create a copy of Session 199 + /// with the given fields replaced by the non-null parameter values. 200 + @JsonKey(includeFromJson: false, includeToJson: false) 201 + @override 202 + @pragma('vm:prefer-inline') 203 + _$$SessionImplCopyWith<_$SessionImpl> get copyWith => 204 + __$$SessionImplCopyWithImpl<_$SessionImpl>(this, _$identity); 205 + 206 + @override 207 + Map<String, dynamic> toJson() { 208 + return _$$SessionImplToJson(this); 209 + } 210 + } 211 + 212 + abstract class _Session implements Session { 213 + const factory _Session({ 214 + required final String token, 215 + required final String refreshToken, 216 + required final DateTime expiresAt, 217 + required final String did, 218 + }) = _$SessionImpl; 219 + 220 + factory _Session.fromJson(Map<String, dynamic> json) = _$SessionImpl.fromJson; 221 + 222 + @override 223 + String get token; 224 + @override 225 + String get refreshToken; 226 + @override 227 + DateTime get expiresAt; 228 + @override 229 + String get did; 230 + 231 + /// Create a copy of Session 232 + /// with the given fields replaced by the non-null parameter values. 233 + @override 234 + @JsonKey(includeFromJson: false, includeToJson: false) 235 + _$$SessionImplCopyWith<_$SessionImpl> get copyWith => 236 + throw _privateConstructorUsedError; 237 + }
+23
lib/models/session.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'session.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$SessionImpl _$$SessionImplFromJson(Map<String, dynamic> json) => 10 + _$SessionImpl( 11 + token: json['token'] as String, 12 + refreshToken: json['refreshToken'] as String, 13 + expiresAt: DateTime.parse(json['expiresAt'] as String), 14 + did: json['did'] as String, 15 + ); 16 + 17 + Map<String, dynamic> _$$SessionImplToJson(_$SessionImpl instance) => 18 + <String, dynamic>{ 19 + 'token': instance.token, 20 + 'refreshToken': instance.refreshToken, 21 + 'expiresAt': instance.expiresAt.toIso8601String(), 22 + 'did': instance.did, 23 + };
+159 -106
lib/providers/gallery_cache_provider.dart
··· 1 import 'dart:async'; 2 import 'dart:io'; 3 - import 'dart:ui' as ui; 4 5 - import 'package:bluesky_text/bluesky_text.dart'; 6 import 'package:flutter/foundation.dart'; 7 import 'package:grain/models/gallery_photo.dart'; 8 import 'package:image_picker/image_picker.dart'; 9 import 'package:riverpod_annotation/riverpod_annotation.dart'; 10 11 import '../api.dart'; 12 import '../models/gallery.dart'; 13 import '../models/gallery_item.dart'; 14 import '../photo_manip.dart'; 15 16 part 'gallery_cache_provider.g.dart'; 17 ··· 39 40 void setGalleriesForActor(String did, List<Gallery> galleries) { 41 setGalleries(galleries); 42 - // Optionally, you could keep a mapping of actor DID to gallery URIs if needed 43 - } 44 - 45 - Future<List<Map<String, dynamic>>> _extractFacets(String text) async { 46 - final blueskyText = BlueskyText(text); 47 - final entities = blueskyText.entities; 48 - final facets = await entities.toFacets(); 49 - return List<Map<String, dynamic>>.from(facets); 50 } 51 52 Future<void> toggleFavorite(String uri) async { ··· 58 bool success = false; 59 String? newFavUri; 60 if (isFav) { 61 - newFavUri = await apiService.createFavorite(galleryUri: uri); 62 - success = newFavUri != null; 63 } else { 64 - success = await apiService.deleteRecord(latestGallery.viewer?.fav ?? uri); 65 newFavUri = null; 66 } 67 if (success) { 68 final newCount = (latestGallery.favCount ?? 0) + (isFav ? 1 : -1); 69 final updatedViewer = latestGallery.viewer?.copyWith(fav: newFavUri); 70 - state = {...state, uri: latestGallery.copyWith(favCount: newCount, viewer: updatedViewer)}; 71 } 72 } 73 ··· 76 final gallery = state[galleryUri]; 77 if (gallery == null) return; 78 // Call backend to delete the gallery item 79 - await apiService.deleteRecord(galleryItemUri); 80 // Remove by gallery item record URI, not photo URI 81 final updatedItems = gallery.items.where((p) => p.gallery?.item != galleryItemUri).toList(); 82 final updatedGallery = gallery.copyWith(items: updatedItems); 83 state = {...state, galleryUri: updatedGallery}; 84 } 85 86 - /// Uploads multiple photos, gets their dimensions, resizes them, creates the photos, and adds them as gallery items. 87 - /// At the end, polls for the updated gallery items and updates the cache. 88 - /// Returns the list of new photoUris if successful, or empty list otherwise. 89 Future<List<String>> uploadAndAddPhotosToGallery({ 90 required String galleryUri, 91 required List<XFile> xfiles, 92 int? startPosition, 93 }) async { 94 // Fetch the latest gallery from the API to avoid stale state 95 final latestGallery = await apiService.getGallery(uri: galleryUri); ··· 101 final int positionOffset = startPosition ?? initialCount; 102 final List<String> photoUris = []; 103 int position = positionOffset; 104 - for (final xfile in xfiles) { 105 - // Resize the image 106 final file = File(xfile.path); 107 final resizedResult = await compute<File, ResizeResult>((f) => resizeImage(file: f), file); 108 // Upload the blob 109 - final blobResult = await apiService.uploadBlob(resizedResult.file); 110 - if (blobResult == null) continue; 111 - // Get image dimensions 112 - final bytes = await xfile.readAsBytes(); 113 - final completer = Completer<Map<String, int>>(); 114 - ui.decodeImageFromList(bytes, (image) { 115 - completer.complete({'width': image.width, 'height': image.height}); 116 - }); 117 - final dims = await completer.future; 118 - // Create the photo 119 - final photoUri = await apiService.createPhoto( 120 - blob: blobResult, 121 - width: dims['width']!, 122 - height: dims['height']!, 123 - ); 124 - if (photoUri == null) continue; 125 // Create the gallery item 126 await apiService.createGalleryItem( 127 - galleryUri: galleryUri, 128 - photoUri: photoUri, 129 - position: position, 130 ); 131 - photoUris.add(photoUri); 132 position++; 133 } 134 - // Poll for updated gallery items 135 - final expectedCount = (gallery?.items.length ?? 0) + photoUris.length; 136 - await apiService.pollGalleryItems(galleryUri: galleryUri, expectedCount: expectedCount); 137 // Fetch the updated gallery and update the cache 138 final updatedGallery = await apiService.getGallery(uri: galleryUri); 139 if (updatedGallery != null) { ··· 148 required String title, 149 required String description, 150 required List<XFile> xfiles, 151 }) async { 152 - // Extract facets from description 153 - final facetsList = await _extractFacets(description); 154 - final facets = facetsList.isEmpty ? null : facetsList; 155 - // Create the gallery with facets 156 - final galleryUri = await apiService.createGallery( 157 - title: title, 158 - description: description, 159 - facets: facets, 160 ); 161 - if (galleryUri == null) return (null, <String>[]); 162 // Upload and add photos 163 - final photoUris = await uploadAndAddPhotosToGallery(galleryUri: galleryUri, xfiles: xfiles); 164 - return (galleryUri, photoUris); 165 } 166 167 - /// Creates gallery items for existing photoUris, polls for updated gallery items, and updates the cache. 168 /// Returns the list of new gallery item URIs if successful, or empty list otherwise. 169 Future<List<String>> addGalleryItemsToGallery({ 170 required String galleryUri, ··· 183 int position = positionOffset; 184 for (final photoUri in photoUris) { 185 // Create the gallery item 186 - final itemUri = await apiService.createGalleryItem( 187 - galleryUri: galleryUri, 188 - photoUri: photoUri, 189 - position: position, 190 ); 191 - if (itemUri != null) { 192 - galleryItemUris.add(itemUri); 193 position++; 194 } 195 } 196 - // Poll for updated gallery items 197 - final expectedCount = (gallery?.items.length ?? 0) + galleryItemUris.length; 198 - await apiService.pollGalleryItems(galleryUri: galleryUri, expectedCount: expectedCount); 199 // Fetch the updated gallery and update the cache 200 final updatedGallery = await apiService.getGallery(uri: galleryUri); 201 if (updatedGallery != null) { ··· 206 207 /// Deletes a gallery from the backend and removes it from the cache. 208 Future<void> deleteGallery(String uri) async { 209 - // Fetch the latest gallery from backend to ensure all items are deleted 210 - final gallery = await apiService.getGallery(uri: uri); 211 - if (gallery != null) { 212 - // Delete all gallery item records 213 - for (final item in gallery.items) { 214 - final itemUri = item.gallery?.item; 215 - if (itemUri != null && itemUri.isNotEmpty) { 216 - await apiService.deleteRecord(itemUri); 217 - } 218 - } 219 - } 220 - await apiService.deleteRecord(uri); 221 removeGallery(uri); 222 } 223 ··· 242 for (int i = 0; i < orderedItems.length; i++) { 243 orderedItems[i] = orderedItems[i].copyWith(position: i); 244 } 245 - await apiService.updateGallerySortOrder(galleryUri: galleryUri, orderedItems: orderedItems); 246 // Update cache with new order 247 final updatedPhotos = orderedItems 248 .where((item) => gallery.items.any((p) => p.uri == item.item)) ··· 255 state = {...state, galleryUri: updatedGallery}; 256 } 257 258 - /// Updates gallery details (title, description), polls for cid change, and updates cache. 259 Future<bool> updateGalleryDetails({ 260 required String galleryUri, 261 required String title, 262 required String description, 263 required String createdAt, 264 }) async { 265 - final prevGallery = state[galleryUri]; 266 - final prevCid = prevGallery?.cid; 267 - // Extract facets from description 268 - final facetsList = await _extractFacets(description); 269 - final facets = facetsList.isEmpty ? null : facetsList; 270 - final success = await apiService.updateGallery( 271 - galleryUri: galleryUri, 272 - title: title, 273 - description: description, 274 - createdAt: createdAt, 275 - facets: facets, 276 - ); 277 - if (success) { 278 - final start = DateTime.now(); 279 - const timeout = Duration(seconds: 20); 280 - const pollInterval = Duration(milliseconds: 1000); 281 - Gallery? updatedGallery; 282 - while (DateTime.now().difference(start) < timeout) { 283 - updatedGallery = await apiService.getGallery(uri: galleryUri); 284 - if (updatedGallery != null && updatedGallery.cid != prevCid) { 285 - break; 286 - } 287 - await Future.delayed(pollInterval); 288 - } 289 - if (updatedGallery != null) { 290 - state = {...state, galleryUri: updatedGallery}; 291 - } 292 } 293 - return success; 294 } 295 296 /// Fetches timeline galleries from the API and updates the cache. ··· 299 final galleries = await apiService.getTimeline(algorithm: algorithm); 300 setGalleries(galleries); 301 return galleries; 302 } 303 }
··· 1 import 'dart:async'; 2 import 'dart:io'; 3 4 import 'package:flutter/foundation.dart'; 5 import 'package:grain/models/gallery_photo.dart'; 6 + import 'package:grain/models/procedures/apply_alts_update.dart'; 7 + import 'package:grain/models/procedures/procedures.dart'; 8 + import 'package:grain/providers/profile_provider.dart'; 9 import 'package:image_picker/image_picker.dart'; 10 import 'package:riverpod_annotation/riverpod_annotation.dart'; 11 12 import '../api.dart'; 13 + import '../app_logger.dart'; 14 import '../models/gallery.dart'; 15 import '../models/gallery_item.dart'; 16 import '../photo_manip.dart'; 17 + import '../utils/exif_utils.dart'; 18 19 part 'gallery_cache_provider.g.dart'; 20 ··· 42 43 void setGalleriesForActor(String did, List<Gallery> galleries) { 44 setGalleries(galleries); 45 } 46 47 Future<void> toggleFavorite(String uri) async { ··· 53 bool success = false; 54 String? newFavUri; 55 if (isFav) { 56 + final response = await apiService.createFavorite( 57 + request: CreateFavoriteRequest(subject: latestGallery.uri), 58 + ); 59 + newFavUri = response.favoriteUri; 60 + success = true; 61 } else { 62 + final deleteResponse = await apiService.deleteFavorite( 63 + request: DeleteFavoriteRequest(uri: latestGallery.viewer?.fav ?? uri), 64 + ); 65 + success = deleteResponse.success; 66 newFavUri = null; 67 } 68 if (success) { 69 final newCount = (latestGallery.favCount ?? 0) + (isFav ? 1 : -1); 70 final updatedViewer = latestGallery.viewer?.copyWith(fav: newFavUri); 71 + final updatedGallery = latestGallery.copyWith(favCount: newCount, viewer: updatedViewer); 72 + state = {...state, uri: updatedGallery}; 73 + 74 + // Push favorite change to profile provider favs 75 + final profileProvider = ref.read( 76 + profileNotifierProvider(apiService.currentUser!.did).notifier, 77 + ); 78 + if (isFav) { 79 + profileProvider.addFavorite(updatedGallery); 80 + } else { 81 + profileProvider.removeFavorite(updatedGallery.uri); 82 + } 83 } 84 } 85 ··· 88 final gallery = state[galleryUri]; 89 if (gallery == null) return; 90 // Call backend to delete the gallery item 91 + await apiService.deleteGalleryItem(request: DeleteGalleryItemRequest(uri: galleryItemUri)); 92 // Remove by gallery item record URI, not photo URI 93 final updatedItems = gallery.items.where((p) => p.gallery?.item != galleryItemUri).toList(); 94 final updatedGallery = gallery.copyWith(items: updatedItems); 95 state = {...state, galleryUri: updatedGallery}; 96 } 97 98 Future<List<String>> uploadAndAddPhotosToGallery({ 99 required String galleryUri, 100 required List<XFile> xfiles, 101 int? startPosition, 102 + bool includeExif = true, 103 + void Function(int imageIndex, double progress)? onProgress, 104 }) async { 105 // Fetch the latest gallery from the API to avoid stale state 106 final latestGallery = await apiService.getGallery(uri: galleryUri); ··· 112 final int positionOffset = startPosition ?? initialCount; 113 final List<String> photoUris = []; 114 int position = positionOffset; 115 + for (int i = 0; i < xfiles.length; i++) { 116 + final xfile = xfiles[i]; 117 + // Report progress if callback is provided 118 + onProgress?.call(i, 0.0); 119 + 120 final file = File(xfile.path); 121 + // Parse EXIF if requested 122 + final exif = includeExif ? await parseAndNormalizeExif(file: file) : null; 123 + 124 + // Simulate progress steps 125 + for (int p = 1; p <= 10; p++) { 126 + await Future.delayed(const Duration(milliseconds: 30)); 127 + onProgress?.call(i, p / 10.0); 128 + } 129 + 130 + // Resize the image 131 final resizedResult = await compute<File, ResizeResult>((f) => resizeImage(file: f), file); 132 // Upload the blob 133 + final res = await apiService.uploadPhoto(resizedResult.file); 134 + if (res.photoUri.isEmpty) continue; 135 + // If EXIF data was found, create photo exif record 136 + if (exif != null) { 137 + await apiService.createExif( 138 + request: CreateExifRequest( 139 + photo: res.photoUri, 140 + dateTimeOriginal: exif['dateTimeOriginal'], 141 + exposureTime: exif['exposureTime'], 142 + fNumber: exif['fNumber'], 143 + flash: exif['flash'], 144 + focalLengthIn35mmFormat: exif['focalLengthIn35mmFilm'], 145 + iSO: exif['iSOSpeedRatings'], 146 + lensMake: exif['lensMake'], 147 + lensModel: exif['lensModel'], 148 + make: exif['make'], 149 + model: exif['model'], 150 + ), 151 + ); 152 + } 153 + 154 // Create the gallery item 155 await apiService.createGalleryItem( 156 + request: CreateGalleryItemRequest( 157 + galleryUri: galleryUri, 158 + photoUri: res.photoUri, 159 + position: position, 160 + ), 161 ); 162 + photoUris.add(res.photoUri); 163 position++; 164 } 165 // Fetch the updated gallery and update the cache 166 final updatedGallery = await apiService.getGallery(uri: galleryUri); 167 if (updatedGallery != null) { ··· 176 required String title, 177 required String description, 178 required List<XFile> xfiles, 179 + bool includeExif = true, 180 + void Function(int imageIndex, double progress)? onProgress, 181 }) async { 182 + final res = await apiService.createGallery( 183 + request: CreateGalleryRequest(title: title, description: description), 184 ); 185 // Upload and add photos 186 + final photoUris = await uploadAndAddPhotosToGallery( 187 + galleryUri: res.galleryUri, 188 + xfiles: xfiles, 189 + includeExif: includeExif, 190 + onProgress: onProgress, 191 + ); 192 + return (res.galleryUri, photoUris); 193 } 194 195 + /// Creates gallery items for existing photoUris and updates the cache. 196 /// Returns the list of new gallery item URIs if successful, or empty list otherwise. 197 Future<List<String>> addGalleryItemsToGallery({ 198 required String galleryUri, ··· 211 int position = positionOffset; 212 for (final photoUri in photoUris) { 213 // Create the gallery item 214 + final res = await apiService.createGalleryItem( 215 + request: CreateGalleryItemRequest( 216 + galleryUri: galleryUri, 217 + photoUri: photoUri, 218 + position: position, 219 + ), 220 ); 221 + if (res.itemUri.isNotEmpty) { 222 + galleryItemUris.add(res.itemUri); 223 position++; 224 } 225 } 226 // Fetch the updated gallery and update the cache 227 final updatedGallery = await apiService.getGallery(uri: galleryUri); 228 if (updatedGallery != null) { ··· 233 234 /// Deletes a gallery from the backend and removes it from the cache. 235 Future<void> deleteGallery(String uri) async { 236 + await apiService.deleteGallery(request: DeleteGalleryRequest(uri: uri)); 237 removeGallery(uri); 238 } 239 ··· 258 for (int i = 0; i < orderedItems.length; i++) { 259 orderedItems[i] = orderedItems[i].copyWith(position: i); 260 } 261 + final res = await apiService.applySort( 262 + request: ApplySortRequest( 263 + writes: orderedItems 264 + .map((item) => ApplySortUpdate(itemUri: item.uri, position: item.position)) 265 + .toList(), 266 + ), 267 + ); 268 + if (!res.success) { 269 + appLogger.w('Failed to reorder gallery items for $galleryUri'); 270 + return; 271 + } 272 // Update cache with new order 273 final updatedPhotos = orderedItems 274 .where((item) => gallery.items.any((p) => p.uri == item.item)) ··· 281 state = {...state, galleryUri: updatedGallery}; 282 } 283 284 + /// Updates gallery details (title, description) and updates cache. 285 Future<bool> updateGalleryDetails({ 286 required String galleryUri, 287 required String title, 288 required String description, 289 required String createdAt, 290 }) async { 291 + try { 292 + await apiService.updateGallery( 293 + request: UpdateGalleryRequest( 294 + galleryUri: galleryUri, 295 + title: title, 296 + description: description, 297 + ), 298 + ); 299 + } catch (e, st) { 300 + appLogger.e('Failed to update gallery details: $e', stackTrace: st); 301 + return false; 302 + } 303 + final updatedGallery = await apiService.getGallery(uri: galleryUri); 304 + if (updatedGallery != null) { 305 + state = {...state, galleryUri: updatedGallery}; 306 } 307 + return true; 308 } 309 310 /// Fetches timeline galleries from the API and updates the cache. ··· 313 final galleries = await apiService.getTimeline(algorithm: algorithm); 314 setGalleries(galleries); 315 return galleries; 316 + } 317 + 318 + /// Updates alt text for multiple photos by calling apiService.updatePhotos, then updates the gallery cache state manually. 319 + /// [galleryUri]: The URI of the gallery containing the photos. 320 + /// [altUpdates]: List of maps with keys: photoUri, alt (and optionally aspectRatio, createdAt, photo). 321 + Future<bool> updatePhotoAltTexts({ 322 + required String galleryUri, 323 + required List<Map<String, dynamic>> altUpdates, 324 + }) async { 325 + final res = await apiService.applyAlts( 326 + request: ApplyAltsRequest( 327 + writes: altUpdates.map((update) { 328 + return ApplyAltsUpdate( 329 + photoUri: update['photoUri'] as String, 330 + alt: update['alt'] as String, 331 + ); 332 + }).toList(), 333 + ), 334 + ); 335 + if (!res.success) return false; 336 + 337 + // Update the gallery photos' alt text in the cache manually 338 + final gallery = state[galleryUri]; 339 + if (gallery == null) return false; 340 + 341 + // Build a map of photoUri to new alt text 342 + final altMap = {for (final update in altUpdates) update['photoUri'] as String: update['alt']}; 343 + 344 + final updatedPhotos = gallery.items.map((photo) { 345 + final newAlt = altMap[photo.uri]; 346 + if (newAlt != null) { 347 + return photo.copyWith(alt: newAlt); 348 + } 349 + return photo; 350 + }).toList(); 351 + 352 + final updatedGallery = gallery.copyWith(items: updatedPhotos); 353 + state = {...state, galleryUri: updatedGallery}; 354 + return true; 355 } 356 }
+1 -1
lib/providers/gallery_cache_provider.g.dart
··· 6 // RiverpodGenerator 7 // ************************************************************************** 8 9 - String _$galleryCacheHash() => r'00313970bd3f1f3b181afc2bc69dd77f0d9fdf8b'; 10 11 /// Holds a cache of galleries by URI. 12 ///
··· 6 // RiverpodGenerator 7 // ************************************************************************** 8 9 + String _$galleryCacheHash() => r'd74ced0d6fcf6369bed80f7f0219bd591c13db5a'; 10 11 /// Holds a cache of galleries by URI. 12 ///
+10 -24
lib/providers/gallery_thread_cache_provider.dart
··· 1 - import 'package:bluesky_text/bluesky_text.dart'; 2 import 'package:grain/api.dart'; 3 import 'package:grain/models/comment.dart'; 4 import 'package:grain/models/gallery.dart'; 5 import 'package:grain/providers/gallery_cache_provider.dart'; 6 import 'package:riverpod_annotation/riverpod_annotation.dart'; 7 ··· 65 } 66 } 67 68 - Future<List<Map<String, dynamic>>> _extractFacets(String text) async { 69 - final blueskyText = BlueskyText(text); 70 - final entities = blueskyText.entities; 71 - final facets = await entities.toFacets(); 72 - return List<Map<String, dynamic>>.from(facets); 73 - } 74 - 75 Future<bool> createComment({required String text, String? replyTo, String? focus}) async { 76 try { 77 - final facetsList = await _extractFacets(text); 78 - final facets = facetsList.isEmpty ? null : facetsList; 79 - final uri = await apiService.createComment( 80 text: text, 81 subject: galleryUri, 82 replyTo: replyTo, 83 - facets: facets, 84 focus: focus, 85 ); 86 - if (uri != null) { 87 - final thread = await apiService.pollGalleryThreadComments( 88 - galleryUri: galleryUri, 89 - expectedCount: state.comments.length + 1, 90 - ); 91 if (thread != null) { 92 state = state.copyWith(gallery: thread.gallery, comments: thread.comments); 93 // Update the gallery cache with the latest gallery ··· 102 } 103 104 Future<bool> deleteComment(Comment comment) async { 105 - final deleted = await apiService.deleteRecord(comment.uri); 106 - if (!deleted) return false; 107 - final expectedCount = state.comments.length - 1; 108 - final thread = await apiService.pollGalleryThreadComments( 109 - galleryUri: galleryUri, 110 - expectedCount: expectedCount, 111 - ); 112 if (thread != null) { 113 state = state.copyWith(gallery: thread.gallery, comments: thread.comments); 114 // Update the gallery cache with the latest gallery
··· 1 import 'package:grain/api.dart'; 2 import 'package:grain/models/comment.dart'; 3 import 'package:grain/models/gallery.dart'; 4 + import 'package:grain/models/procedures/create_comment_request.dart'; 5 + import 'package:grain/models/procedures/delete_comment_request.dart'; 6 import 'package:grain/providers/gallery_cache_provider.dart'; 7 import 'package:riverpod_annotation/riverpod_annotation.dart'; 8 ··· 66 } 67 } 68 69 Future<bool> createComment({required String text, String? replyTo, String? focus}) async { 70 try { 71 + final request = CreateCommentRequest( 72 text: text, 73 subject: galleryUri, 74 replyTo: replyTo, 75 focus: focus, 76 ); 77 + final res = await apiService.createComment(request: request); 78 + if (res.commentUri.isNotEmpty) { 79 + final thread = await apiService.getGalleryThread(uri: galleryUri); 80 if (thread != null) { 81 state = state.copyWith(gallery: thread.gallery, comments: thread.comments); 82 // Update the gallery cache with the latest gallery ··· 91 } 92 93 Future<bool> deleteComment(Comment comment) async { 94 + final request = DeleteCommentRequest(uri: comment.uri); 95 + final res = await apiService.deleteComment(request: request); 96 + if (!res.success) return false; 97 + final thread = await apiService.getGalleryThread(uri: galleryUri); 98 if (thread != null) { 99 state = state.copyWith(gallery: thread.gallery, comments: thread.comments); 100 // Update the gallery cache with the latest gallery
+1 -1
lib/providers/gallery_thread_cache_provider.g.dart
··· 6 // RiverpodGenerator 7 // ************************************************************************** 8 9 - String _$galleryThreadHash() => r'fa270baa7dd229fdd6b4e2610cf232aed53ed3db'; 10 11 /// Copied from Dart SDK 12 class _SystemHash {
··· 6 // RiverpodGenerator 7 // ************************************************************************** 8 9 + String _$galleryThreadHash() => r'80fa9b4e19b9d7606654fff8549516d4654dfa87'; 10 11 /// Copied from Dart SDK 12 class _SystemHash {
+87
lib/providers/notifications_provider.dart
···
··· 1 + import 'dart:async'; 2 + import 'dart:convert'; 3 + 4 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 + import 'package:grain/api.dart'; 6 + import 'package:grain/auth.dart'; 7 + import 'package:grain/main.dart'; 8 + import 'package:grain/models/notification.dart'; 9 + import 'package:grain/websocket_service.dart'; 10 + 11 + final notificationsProvider = 12 + StateNotifierProvider<NotificationsNotifier, AsyncValue<List<Notification>>>( 13 + (ref) => NotificationsNotifier(), 14 + ); 15 + 16 + class NotificationsNotifier extends StateNotifier<AsyncValue<List<Notification>>> { 17 + WebSocketService? _wsService; 18 + 19 + NotificationsNotifier() : super(const AsyncValue.loading()) { 20 + _connectAndListen(); 21 + } 22 + 23 + void _connectAndListen() async { 24 + final session = await auth.getValidSession(); 25 + final accessToken = session?.token; 26 + final wsUrl = AppConfig.wsUrl; 27 + if (accessToken == null) { 28 + state = AsyncValue.error('No access token', StackTrace.current); 29 + return; 30 + } 31 + _wsService = WebSocketService( 32 + wsUrl: wsUrl, 33 + accessToken: accessToken, 34 + onMessage: (message) async { 35 + try { 36 + final data = message is String ? jsonDecode(message) : message; 37 + if (data is Map<String, dynamic> && data['type'] == 'refresh-notifications') { 38 + final notifications = await apiService.getNotifications(); 39 + if (mounted) state = AsyncValue.data(notifications); 40 + } else { 41 + final notification = Notification.fromJson(data); 42 + if (mounted) { 43 + final current = state.value ?? []; 44 + state = AsyncValue.data([...current, notification]); 45 + } 46 + } 47 + } catch (e) { 48 + // Handle parse error or ignore non-notification messages 49 + } 50 + }, 51 + ); 52 + try { 53 + final notifications = await apiService.getNotifications(); 54 + if (mounted) state = AsyncValue.data(notifications); 55 + } catch (e, st) { 56 + if (mounted) state = AsyncValue.error(e, st); 57 + } 58 + await _wsService!.connect(); 59 + } 60 + 61 + /// Fetch notifications directly from the API (without websocket) 62 + Future<void> fetch() async { 63 + state = const AsyncValue.loading(); 64 + try { 65 + final notifications = await apiService.getNotifications(); 66 + if (mounted) { 67 + state = AsyncValue.data(notifications); 68 + } 69 + } catch (e, st) { 70 + if (mounted) state = AsyncValue.error(e, st); 71 + } 72 + } 73 + 74 + /// Marks all notifications as seen both on the server and locally. 75 + Future<void> updateSeen() async { 76 + final success = await apiService.updateSeen(); 77 + if (success && mounted && state.value != null) { 78 + state = AsyncValue.data([for (final n in state.value!) n.copyWith(isRead: true)]); 79 + } 80 + } 81 + 82 + @override 83 + void dispose() { 84 + _wsService?.disconnect(); 85 + super.dispose(); 86 + } 87 + }
+55 -39
lib/providers/profile_provider.dart
··· 1 import 'dart:io'; 2 3 import 'package:bluesky_text/bluesky_text.dart'; 4 import 'package:grain/api.dart'; 5 import 'package:grain/models/gallery.dart'; 6 - import 'package:grain/models/profile.dart'; 7 import 'package:grain/models/profile_with_galleries.dart'; 8 import 'package:grain/providers/gallery_cache_provider.dart'; 9 import 'package:image_picker/image_picker.dart'; 10 import 'package:riverpod_annotation/riverpod_annotation.dart'; ··· 82 required String description, 83 dynamic avatarFile, 84 }) async { 85 - final currentProfile = state.value?.profile; 86 - final isUnchanged = 87 - currentProfile != null && 88 - currentProfile.displayName == displayName && 89 - currentProfile.description == description && 90 - avatarFile == null; 91 - if (isUnchanged) { 92 - // No changes, skip API call 93 - return true; 94 - } 95 File? file; 96 if (avatarFile is XFile) { 97 file = File(avatarFile.path); ··· 100 } else { 101 file = null; 102 } 103 - final success = await apiService.updateProfile( 104 - displayName: displayName, 105 - description: description, 106 - avatarFile: file, 107 ); 108 - if (success) { 109 - final start = DateTime.now(); 110 - const timeout = Duration(seconds: 20); 111 - const pollInterval = Duration(milliseconds: 1000); 112 - Profile? updated; 113 - final prevCid = state.value?.profile.cid; 114 - state = const AsyncValue.loading(); // Force UI to show loading and rebuild 115 - while (DateTime.now().difference(start) < timeout) { 116 - updated = await apiService.fetchProfile(did: did); 117 - if (updated != null && updated.cid != prevCid) { 118 - break; 119 - } 120 - await Future.delayed(pollInterval); 121 - } 122 - // Always assign a new instance to state 123 if (updated != null) { 124 final galleries = await apiService.fetchActorGalleries(did: did); 125 final facets = await computeAndFilterFacets(updated.description); 126 - // Update the gallery cache provider 127 ref.read(galleryCacheProvider.notifier).setGalleriesForActor(did, galleries); 128 state = AsyncValue.data( 129 ProfileWithGalleries( ··· 134 ); 135 } else { 136 state = const AsyncValue.data(null); 137 - } 138 - if (updated == null) { 139 await refresh(); 140 } 141 } 142 - return success; 143 } 144 145 Future<void> toggleFollow(String? followerDid) async { ··· 150 final followUri = viewer?.following; 151 if (followUri != null && followUri.isNotEmpty) { 152 // Unfollow 153 - final success = await apiService.deleteRecord(followUri); 154 - if (success) { 155 final updatedProfile = profile.copyWith( 156 viewer: viewer?.copyWith(following: null), 157 followersCount: (profile.followersCount ?? 1) - 1, ··· 166 } 167 } else { 168 // Follow 169 - final newFollowUri = await apiService.createFollow(followeeDid: did); 170 - if (newFollowUri != null) { 171 final updatedProfile = profile.copyWith( 172 - viewer: viewer?.copyWith(following: newFollowUri), 173 followersCount: (profile.followersCount ?? 0) + 1, 174 ); 175 state = AsyncValue.data( ··· 212 profile: updatedProfile, 213 galleries: updatedGalleries, 214 favs: currentProfile.favs, 215 ), 216 ); 217 }
··· 1 import 'dart:io'; 2 3 import 'package:bluesky_text/bluesky_text.dart'; 4 + import 'package:flutter/foundation.dart'; 5 import 'package:grain/api.dart'; 6 import 'package:grain/models/gallery.dart'; 7 + import 'package:grain/models/procedures/create_follow_request.dart'; 8 + import 'package:grain/models/procedures/delete_follow_request.dart'; 9 + import 'package:grain/models/procedures/update_profile_request.dart'; 10 import 'package:grain/models/profile_with_galleries.dart'; 11 + import 'package:grain/photo_manip.dart'; 12 import 'package:grain/providers/gallery_cache_provider.dart'; 13 import 'package:image_picker/image_picker.dart'; 14 import 'package:riverpod_annotation/riverpod_annotation.dart'; ··· 86 required String description, 87 dynamic avatarFile, 88 }) async { 89 File? file; 90 if (avatarFile is XFile) { 91 file = File(avatarFile.path); ··· 94 } else { 95 file = null; 96 } 97 + final profileRes = await apiService.updateProfile( 98 + request: UpdateProfileRequest(displayName: displayName, description: description), 99 ); 100 + bool avatarSuccess = true; 101 + if (file != null) { 102 + final resizedResult = await compute<File, ResizeResult>((f) => resizeImage(file: f), file); 103 + final avatarRes = await apiService.updateAvatar(avatarFile: resizedResult.file); 104 + avatarSuccess = avatarRes.success; 105 + } 106 + // Refetch updated profile if update succeeded 107 + if (profileRes.success || avatarSuccess) { 108 + final updated = await apiService.fetchProfile(did: did); 109 if (updated != null) { 110 final galleries = await apiService.fetchActorGalleries(did: did); 111 final facets = await computeAndFilterFacets(updated.description); 112 ref.read(galleryCacheProvider.notifier).setGalleriesForActor(did, galleries); 113 state = AsyncValue.data( 114 ProfileWithGalleries( ··· 119 ); 120 } else { 121 state = const AsyncValue.data(null); 122 await refresh(); 123 } 124 + return true; 125 + } else { 126 + await refresh(); 127 + return false; 128 } 129 } 130 131 Future<void> toggleFollow(String? followerDid) async { ··· 136 final followUri = viewer?.following; 137 if (followUri != null && followUri.isNotEmpty) { 138 // Unfollow 139 + final res = await apiService.deleteFollow(request: DeleteFollowRequest(uri: followUri)); 140 + if (res.success) { 141 final updatedProfile = profile.copyWith( 142 viewer: viewer?.copyWith(following: null), 143 followersCount: (profile.followersCount ?? 1) - 1, ··· 152 } 153 } else { 154 // Follow 155 + final res = await apiService.createFollow(request: CreateFollowRequest(subject: followerDid)); 156 + if (res.followUri.isNotEmpty) { 157 final updatedProfile = profile.copyWith( 158 + viewer: viewer?.copyWith(following: res.followUri), 159 followersCount: (profile.followersCount ?? 0) + 1, 160 ); 161 state = AsyncValue.data( ··· 198 profile: updatedProfile, 199 galleries: updatedGalleries, 200 favs: currentProfile.favs, 201 + ), 202 + ); 203 + } 204 + } 205 + 206 + /// Adds a gallery to the user's favorites in the profile state. 207 + void addFavorite(Gallery gallery) { 208 + final currentProfile = state.value; 209 + if (currentProfile != null) { 210 + final updatedFavs = [gallery, ...?currentProfile.favs]; 211 + state = AsyncValue.data( 212 + ProfileWithGalleries( 213 + profile: currentProfile.profile, 214 + galleries: currentProfile.galleries, 215 + favs: updatedFavs, 216 + ), 217 + ); 218 + } 219 + } 220 + 221 + /// Removes a gallery from the user's favorites in the profile state by URI. 222 + void removeFavorite(String galleryUri) { 223 + final currentProfile = state.value; 224 + if (currentProfile != null) { 225 + final updatedFavs = (currentProfile.favs ?? []).where((g) => g.uri != galleryUri).toList(); 226 + state = AsyncValue.data( 227 + ProfileWithGalleries( 228 + profile: currentProfile.profile, 229 + galleries: currentProfile.galleries, 230 + favs: updatedFavs, 231 ), 232 ); 233 }
+1 -1
lib/providers/profile_provider.g.dart
··· 6 // RiverpodGenerator 7 // ************************************************************************** 8 9 - String _$profileNotifierHash() => r'2ec9237c3a60bff58175d2d39f37b050eecdba78'; 10 11 /// Copied from Dart SDK 12 class _SystemHash {
··· 6 // RiverpodGenerator 7 // ************************************************************************** 8 9 + String _$profileNotifierHash() => r'48159a8319bba2f2ec5462c50d80ba6a5b72d91e'; 10 11 /// Copied from Dart SDK 12 class _SystemHash {
+136
lib/screens/edit_alt_text_sheet.dart
···
··· 1 + import 'package:flutter/cupertino.dart'; 2 + import 'package:flutter/material.dart'; 3 + import 'package:flutter/services.dart'; 4 + import 'package:grain/models/gallery_photo.dart'; 5 + import 'package:grain/widgets/app_image.dart'; 6 + 7 + class EditAltTextSheet extends StatefulWidget { 8 + final List<GalleryPhoto> photos; 9 + final void Function(Map<String, String?>) onSave; 10 + 11 + const EditAltTextSheet({super.key, required this.photos, required this.onSave}); 12 + 13 + @override 14 + State<EditAltTextSheet> createState() => _EditAltTextSheetState(); 15 + } 16 + 17 + class _EditAltTextSheetState extends State<EditAltTextSheet> { 18 + late Map<String, TextEditingController> _controllers; 19 + 20 + @override 21 + void initState() { 22 + super.initState(); 23 + _controllers = { 24 + for (final photo in widget.photos) photo.uri: TextEditingController(text: photo.alt ?? ''), 25 + }; 26 + } 27 + 28 + @override 29 + void dispose() { 30 + for (final c in _controllers.values) { 31 + c.dispose(); 32 + } 33 + super.dispose(); 34 + } 35 + 36 + void _onSave() { 37 + final altTexts = {for (final photo in widget.photos) photo.uri: _controllers[photo.uri]?.text}; 38 + widget.onSave(altTexts); 39 + Navigator.of(context).pop(); 40 + } 41 + 42 + @override 43 + Widget build(BuildContext context) { 44 + final theme = Theme.of(context); 45 + return CupertinoPageScaffold( 46 + backgroundColor: theme.colorScheme.surface, 47 + navigationBar: CupertinoNavigationBar( 48 + backgroundColor: theme.colorScheme.surface, 49 + middle: Text( 50 + 'Edit alt text', 51 + style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600), 52 + ), 53 + leading: CupertinoButton( 54 + padding: EdgeInsets.zero, 55 + child: const Text('Cancel'), 56 + onPressed: () => Navigator.of(context).pop(), 57 + ), 58 + trailing: CupertinoButton( 59 + padding: EdgeInsets.zero, 60 + onPressed: _onSave, 61 + child: const Text('Save'), 62 + ), 63 + ), 64 + child: SafeArea( 65 + child: ListView.separated( 66 + padding: const EdgeInsets.all(16), 67 + itemCount: widget.photos.length, 68 + separatorBuilder: (_, __) => const SizedBox(height: 12), 69 + itemBuilder: (context, index) { 70 + final theme = Theme.of(context); 71 + final photo = widget.photos[index]; 72 + final width = photo.aspectRatio?.width; 73 + final height = photo.aspectRatio?.height; 74 + return Row( 75 + crossAxisAlignment: CrossAxisAlignment.center, 76 + children: [ 77 + SizedBox( 78 + width: 64, 79 + child: AspectRatio( 80 + aspectRatio: (width != null && height != null && width > 0 && height > 0) 81 + ? width / height 82 + : 1.0, 83 + child: AppImage( 84 + url: photo.thumb ?? photo.fullsize, 85 + borderRadius: BorderRadius.circular(8), 86 + ), 87 + ), 88 + ), 89 + const SizedBox(width: 16), 90 + Expanded( 91 + child: TextField( 92 + controller: _controllers[photo.uri], 93 + decoration: InputDecoration( 94 + hintText: 'Enter alt text', 95 + border: InputBorder.none, 96 + filled: false, 97 + contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), 98 + hintStyle: theme.textTheme.bodyMedium?.copyWith(color: theme.hintColor), 99 + ), 100 + style: theme.textTheme.bodyMedium, 101 + cursorColor: theme.colorScheme.primary, 102 + minLines: 1, 103 + maxLines: 6, 104 + textAlignVertical: TextAlignVertical.top, 105 + scrollPhysics: const AlwaysScrollableScrollPhysics(), 106 + keyboardType: TextInputType.multiline, 107 + ), 108 + ), 109 + ], 110 + ); 111 + }, 112 + ), 113 + ), 114 + ); 115 + } 116 + } 117 + 118 + Future<void> showEditAltTextSheet( 119 + BuildContext context, { 120 + required List<GalleryPhoto> photos, 121 + required void Function(Map<String, String?>) onSave, 122 + }) async { 123 + final theme = Theme.of(context); 124 + await showCupertinoSheet( 125 + context: context, 126 + useNestedNavigation: false, 127 + pageBuilder: (context) => Material( 128 + type: MaterialType.transparency, 129 + child: EditAltTextSheet(photos: photos, onSave: onSave), 130 + ), 131 + ); 132 + // Restore status bar style or any other cleanup 133 + SystemChrome.setSystemUIOverlayStyle( 134 + theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, 135 + ); 136 + }
+174 -6
lib/screens/explore_page.dart
··· 1 import 'dart:async'; 2 3 import 'package:flutter/material.dart'; 4 import 'package:grain/api.dart'; 5 import 'package:grain/models/profile.dart'; 6 import 'package:grain/widgets/app_image.dart'; 7 import 'package:grain/widgets/plain_text_field.dart'; 8 - 9 - import 'profile_page.dart'; 10 11 - class ExplorePage extends StatefulWidget { 12 const ExplorePage({super.key}); 13 14 @override 15 - State<ExplorePage> createState() => _ExplorePageState(); 16 } 17 18 - class _ExplorePageState extends State<ExplorePage> { 19 final TextEditingController _controller = TextEditingController(); 20 List<Profile> _results = []; 21 bool _loading = false; 22 bool _searched = false; 23 Timer? _debounce; ··· 25 @override 26 void initState() { 27 super.initState(); 28 } 29 30 void _onSearchChanged(String value) { ··· 81 hintText: 'Search for users', 82 onChanged: _onSearchChanged, 83 enabled: true, 84 ), 85 ), 86 if (_controller.text.isNotEmpty) 87 Padding( 88 padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4), ··· 130 : CircleAvatar( 131 radius: 16, 132 backgroundColor: theme.colorScheme.surfaceContainerHighest, 133 - child: Icon(Icons.account_circle, color: theme.iconTheme.color), 134 ), 135 title: Text( 136 profile.displayName?.isNotEmpty == true ? profile.displayName! : '@${profile.handle}', ··· 148 setState(() { 149 _searched = false; 150 _loading = false; 151 }); 152 if (context.mounted) { 153 Navigator.of(context).push( 154 MaterialPageRoute(
··· 1 import 'dart:async'; 2 3 import 'package:flutter/material.dart'; 4 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 import 'package:grain/api.dart'; 6 + import 'package:grain/app_icons.dart'; 7 import 'package:grain/models/profile.dart'; 8 + import 'package:grain/providers/profile_provider.dart'; 9 + import 'package:grain/screens/profile_page.dart'; 10 import 'package:grain/widgets/app_image.dart'; 11 import 'package:grain/widgets/plain_text_field.dart'; 12 + import 'package:shared_preferences/shared_preferences.dart'; 13 14 + class ExplorePage extends ConsumerStatefulWidget { 15 const ExplorePage({super.key}); 16 17 @override 18 + ConsumerState<ExplorePage> createState() => _ExplorePageState(); 19 } 20 21 + class _ExplorePageState extends ConsumerState<ExplorePage> { 22 final TextEditingController _controller = TextEditingController(); 23 List<Profile> _results = []; 24 + List<Profile> _recentlySearched = []; 25 + static const String _recentlySearchedKey = 'recently_searched_dids'; 26 bool _loading = false; 27 bool _searched = false; 28 Timer? _debounce; ··· 30 @override 31 void initState() { 32 super.initState(); 33 + _loadRecentlySearched(); 34 + } 35 + 36 + Future<void> _loadRecentlySearched() async { 37 + final prefs = await SharedPreferences.getInstance(); 38 + final dids = prefs.getStringList(_recentlySearchedKey) ?? []; 39 + if (dids.isNotEmpty) { 40 + final profiles = <Profile>[]; 41 + for (final did in dids) { 42 + try { 43 + final profileWithGalleries = await ref.read(profileNotifierProvider(did).future); 44 + if (profileWithGalleries != null) { 45 + profiles.add(profileWithGalleries.profile); 46 + } 47 + } catch (_) {} 48 + } 49 + if (mounted) { 50 + setState(() { 51 + _recentlySearched = profiles; 52 + }); 53 + } 54 + } 55 + } 56 + 57 + Future<void> _saveRecentlySearched() async { 58 + final prefs = await SharedPreferences.getInstance(); 59 + final dids = _recentlySearched.map((p) => p.did).toList(); 60 + await prefs.setStringList(_recentlySearchedKey, dids); 61 } 62 63 void _onSearchChanged(String value) { ··· 114 hintText: 'Search for users', 115 onChanged: _onSearchChanged, 116 enabled: true, 117 + prefixIcon: Icon(AppIcons.search, color: theme.iconTheme.color, size: 24), 118 + suffixIcon: _controller.text.isNotEmpty 119 + ? GestureDetector( 120 + onTap: () { 121 + _controller.clear(); 122 + _onSearchChanged(''); 123 + }, 124 + child: Icon(Icons.clear, color: theme.iconTheme.color, size: 22), 125 + ) 126 + : null, 127 ), 128 ), 129 + if (_controller.text.isEmpty && _recentlySearched.isNotEmpty) 130 + Padding( 131 + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), 132 + child: Column( 133 + crossAxisAlignment: CrossAxisAlignment.start, 134 + children: [ 135 + Padding( 136 + padding: const EdgeInsets.only(left: 4.0, bottom: 4.0), 137 + child: Text( 138 + 'Recent Searches', 139 + style: theme.textTheme.titleSmall?.copyWith( 140 + fontWeight: FontWeight.bold, 141 + color: theme.textTheme.bodyMedium?.color, 142 + ), 143 + ), 144 + ), 145 + SizedBox( 146 + height: 96, 147 + child: ListView.separated( 148 + scrollDirection: Axis.horizontal, 149 + itemCount: _recentlySearched.length, 150 + separatorBuilder: (context, index) => SizedBox(width: 16), 151 + itemBuilder: (context, index) { 152 + final profile = _recentlySearched[index]; 153 + return SizedBox( 154 + width: 80, 155 + child: Stack( 156 + children: [ 157 + SizedBox( 158 + width: 80, 159 + height: 96, 160 + child: Column( 161 + mainAxisAlignment: MainAxisAlignment.center, 162 + children: [ 163 + Expanded( 164 + child: GestureDetector( 165 + onTap: () async { 166 + FocusScope.of(context).unfocus(); 167 + _debounce?.cancel(); 168 + setState(() { 169 + _searched = false; 170 + _loading = false; 171 + }); 172 + if (context.mounted) { 173 + Navigator.of(context).push( 174 + MaterialPageRoute( 175 + builder: (context) => 176 + ProfilePage(did: profile.did, showAppBar: true), 177 + ), 178 + ); 179 + } 180 + }, 181 + child: CircleAvatar( 182 + radius: 32, 183 + backgroundColor: theme.colorScheme.surfaceContainerHighest, 184 + backgroundImage: profile.avatar?.isNotEmpty == true 185 + ? NetworkImage(profile.avatar!) 186 + : null, 187 + child: profile.avatar?.isNotEmpty == true 188 + ? null 189 + : Icon( 190 + AppIcons.accountCircle, 191 + color: theme.iconTheme.color, 192 + size: 40, 193 + ), 194 + ), 195 + ), 196 + ), 197 + SizedBox(height: 4), 198 + SizedBox( 199 + width: 80, 200 + child: Text( 201 + profile.displayName?.isNotEmpty == true 202 + ? profile.displayName! 203 + : '@${profile.handle}', 204 + style: theme.textTheme.bodyMedium, 205 + maxLines: 1, 206 + overflow: TextOverflow.ellipsis, 207 + textAlign: TextAlign.center, 208 + ), 209 + ), 210 + ], 211 + ), 212 + ), 213 + Positioned( 214 + top: 4, 215 + right: 4, 216 + child: GestureDetector( 217 + onTap: () async { 218 + setState(() { 219 + _recentlySearched.removeAt(index); 220 + }); 221 + await _saveRecentlySearched(); 222 + }, 223 + child: Container( 224 + padding: const EdgeInsets.all(2), 225 + decoration: BoxDecoration( 226 + color: theme.colorScheme.surface, 227 + shape: BoxShape.circle, 228 + boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 2)], 229 + ), 230 + child: Icon(Icons.close, size: 18, color: theme.iconTheme.color), 231 + ), 232 + ), 233 + ), 234 + ], 235 + ), 236 + ); 237 + }, 238 + ), 239 + ), 240 + ], 241 + ), 242 + ), 243 if (_controller.text.isNotEmpty) 244 Padding( 245 padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4), ··· 287 : CircleAvatar( 288 radius: 16, 289 backgroundColor: theme.colorScheme.surfaceContainerHighest, 290 + child: Icon(AppIcons.accountCircle, color: theme.iconTheme.color), 291 ), 292 title: Text( 293 profile.displayName?.isNotEmpty == true ? profile.displayName! : '@${profile.handle}', ··· 305 setState(() { 306 _searched = false; 307 _loading = false; 308 + // Move to front if already present, else add to front 309 + final existingIndex = _recentlySearched.indexWhere((p) => p.did == profile.did); 310 + if (existingIndex != -1) { 311 + _recentlySearched.removeAt(existingIndex); 312 + } 313 + _recentlySearched.insert(0, profile); 314 + // Limit to 10 recent users 315 + if (_recentlySearched.length > 10) { 316 + _recentlySearched.removeLast(); 317 + } 318 }); 319 + await _saveRecentlySearched(); 320 if (context.mounted) { 321 Navigator.of(context).push( 322 MaterialPageRoute(
+15 -4
lib/screens/gallery_action_sheet.dart
··· 1 import 'package:flutter/material.dart'; 2 3 class GalleryActionSheet extends StatelessWidget { 4 final VoidCallback? onEditDetails; ··· 6 final VoidCallback? onChangeSortOrder; 7 final Future<void> Function(BuildContext parentContext)? onDeleteGallery; 8 final BuildContext parentContext; 9 10 const GalleryActionSheet({ 11 super.key, 12 required this.parentContext, 13 this.onEditDetails, 14 this.onEditPhotos, 15 this.onChangeSortOrder, 16 this.onDeleteGallery, 17 }); ··· 23 mainAxisSize: MainAxisSize.min, 24 children: [ 25 ListTile( 26 - leading: const Icon(Icons.edit), 27 title: const Text('Edit details'), 28 onTap: () { 29 Navigator.of(context).pop(); ··· 31 }, 32 ), 33 ListTile( 34 - leading: const Icon(Icons.photo_library), 35 title: const Text('Edit photos'), 36 onTap: () { 37 Navigator.of(context).pop(); ··· 39 }, 40 ), 41 ListTile( 42 - leading: const Icon(Icons.sort), 43 title: const Text('Edit sort order'), 44 onTap: () { 45 Navigator.of(context).pop(); ··· 47 }, 48 ), 49 ListTile( 50 - leading: const Icon(Icons.delete, color: Colors.red), 51 title: const Text('Delete gallery', style: TextStyle(color: Colors.red)), 52 onTap: () async { 53 Navigator.of(context).pop();
··· 1 import 'package:flutter/material.dart'; 2 + import 'package:grain/app_icons.dart'; 3 4 class GalleryActionSheet extends StatelessWidget { 5 final VoidCallback? onEditDetails; ··· 7 final VoidCallback? onChangeSortOrder; 8 final Future<void> Function(BuildContext parentContext)? onDeleteGallery; 9 final BuildContext parentContext; 10 + final VoidCallback? onEditAltText; 11 12 const GalleryActionSheet({ 13 super.key, 14 required this.parentContext, 15 this.onEditDetails, 16 this.onEditPhotos, 17 + this.onEditAltText, 18 this.onChangeSortOrder, 19 this.onDeleteGallery, 20 }); ··· 26 mainAxisSize: MainAxisSize.min, 27 children: [ 28 ListTile( 29 + leading: Icon(AppIcons.edit), 30 title: const Text('Edit details'), 31 onTap: () { 32 Navigator.of(context).pop(); ··· 34 }, 35 ), 36 ListTile( 37 + leading: Icon(AppIcons.photoLibrary), 38 title: const Text('Edit photos'), 39 onTap: () { 40 Navigator.of(context).pop(); ··· 42 }, 43 ), 44 ListTile( 45 + leading: Icon(AppIcons.edit), 46 + title: const Text('Edit alt text'), 47 + onTap: () { 48 + Navigator.of(context).pop(); 49 + if (onEditAltText != null) onEditAltText!(); 50 + }, 51 + ), 52 + ListTile( 53 + leading: Icon(AppIcons.sort), 54 title: const Text('Edit sort order'), 55 onTap: () { 56 Navigator.of(context).pop(); ··· 58 }, 59 ), 60 ListTile( 61 + leading: Icon(AppIcons.delete, color: Colors.red), 62 title: const Text('Delete gallery', style: TextStyle(color: Colors.red)), 63 onTap: () async { 64 Navigator.of(context).pop();
+34 -6
lib/screens/gallery_edit_photos_sheet.dart
··· 3 import 'package:flutter/services.dart'; 4 import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 import 'package:grain/api.dart'; 6 import 'package:grain/models/gallery_photo.dart'; 7 import 'package:grain/providers/gallery_cache_provider.dart'; 8 import 'package:grain/widgets/app_button.dart'; ··· 31 late List<GalleryPhoto> _photos; 32 bool _loading = false; 33 int? _deletingPhotoIndex; 34 35 @override 36 void initState() { ··· 100 clipBehavior: Clip.antiAlias, 101 child: photo.thumb != null && photo.thumb!.isNotEmpty 102 ? AppImage(url: photo.thumb!, fit: BoxFit.cover) 103 - : const Icon(Icons.photo, size: 48), 104 ), 105 Positioned( 106 top: 8, ··· 161 valueColor: AlwaysStoppedAnimation<Color>(Colors.white), 162 ), 163 ) 164 - : const Icon(Icons.close, color: Colors.white, size: 20), 165 ), 166 ), 167 ), ··· 190 await notifier.uploadAndAddPhotosToGallery( 191 galleryUri: widget.galleryUri, 192 xfiles: picked, 193 ); 194 // Fetch the updated gallery from provider state 195 final updatedGallery = ref.read( ··· 219 context, 220 actorDid: actorDid, 221 galleryUri: widget.galleryUri, 222 - onSelect: (photos) { 223 - setState(() { 224 - _photos.addAll(photos); 225 - }); 226 }, 227 ); 228 }, 229 ), 230 ), 231 ], 232 ),
··· 3 import 'package:flutter/services.dart'; 4 import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 import 'package:grain/api.dart'; 6 + import 'package:grain/app_icons.dart'; 7 import 'package:grain/models/gallery_photo.dart'; 8 import 'package:grain/providers/gallery_cache_provider.dart'; 9 import 'package:grain/widgets/app_button.dart'; ··· 32 late List<GalleryPhoto> _photos; 33 bool _loading = false; 34 int? _deletingPhotoIndex; 35 + bool _includeExif = true; 36 37 @override 38 void initState() { ··· 102 clipBehavior: Clip.antiAlias, 103 child: photo.thumb != null && photo.thumb!.isNotEmpty 104 ? AppImage(url: photo.thumb!, fit: BoxFit.cover) 105 + : const Icon(AppIcons.photo, size: 48), 106 ), 107 Positioned( 108 top: 8, ··· 163 valueColor: AlwaysStoppedAnimation<Color>(Colors.white), 164 ), 165 ) 166 + : const Icon(AppIcons.close, color: Colors.white, size: 20), 167 ), 168 ), 169 ), ··· 192 await notifier.uploadAndAddPhotosToGallery( 193 galleryUri: widget.galleryUri, 194 xfiles: picked, 195 + includeExif: _includeExif, 196 ); 197 // Fetch the updated gallery from provider state 198 final updatedGallery = ref.read( ··· 222 context, 223 actorDid: actorDid, 224 galleryUri: widget.galleryUri, 225 + onSelect: (photos) async { 226 + // Wait for provider to update after adding 227 + await Future.delayed(const Duration(milliseconds: 100)); 228 + final updatedGallery = ref.read( 229 + galleryCacheProvider, 230 + )[widget.galleryUri]; 231 + if (updatedGallery != null && mounted) { 232 + setState(() { 233 + _photos = List.from(updatedGallery.items); 234 + }); 235 + } 236 }, 237 ); 238 }, 239 ), 240 + ), 241 + ], 242 + ), 243 + const SizedBox(height: 16), 244 + Row( 245 + children: [ 246 + Expanded( 247 + child: Text('Include image metadata (EXIF)', style: theme.textTheme.bodyMedium), 248 + ), 249 + Switch( 250 + value: _includeExif, 251 + onChanged: (_loading || _deletingPhotoIndex != null) 252 + ? null 253 + : (val) { 254 + setState(() { 255 + _includeExif = val; 256 + }); 257 + }, 258 ), 259 ], 260 ),
+30 -13
lib/screens/gallery_page.dart
··· 1 import 'package:flutter/material.dart'; 2 import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:grain/api.dart'; 4 import 'package:grain/models/gallery_photo.dart'; 5 import 'package:grain/providers/gallery_cache_provider.dart'; 6 import 'package:grain/providers/profile_provider.dart'; 7 import 'package:grain/screens/comments_page.dart'; 8 import 'package:grain/screens/create_gallery_page.dart'; 9 import 'package:grain/screens/gallery_action_sheet.dart'; 10 import 'package:grain/screens/gallery_edit_photos_sheet.dart'; 11 import 'package:grain/screens/gallery_sort_order_sheet.dart'; ··· 119 actions: [ 120 if (gallery.creator?.did == widget.currentUserDid) 121 IconButton( 122 - icon: const Icon(Icons.more_vert), 123 tooltip: 'Gallery Actions', 124 onPressed: () async { 125 showModalBottomSheet( ··· 137 allPhotos: gallery.items, 138 onSave: (newSelection) { 139 // TODO: Save new selection to backend and refresh gallery 140 }, 141 ); 142 }, ··· 148 await ref 149 .read(galleryCacheProvider.notifier) 150 .reorderGalleryItems(galleryUri: gallery.uri, newOrder: newOrder); 151 - await _maybeFetchGallery(forceRefresh: true); 152 if (!sheetContext.mounted) return; 153 Navigator.of(sheetContext).pop(); 154 if (!mounted) return; ··· 225 (gallery.creator == null || 226 (gallery.creator!.avatar?.isNotEmpty != true)) 227 ? Icon( 228 - Icons.account_circle, 229 size: 24, 230 color: theme.colorScheme.onSurface.withOpacity(0.4), 231 ) ··· 254 ); 255 } 256 : null, 257 - child: Row( 258 - crossAxisAlignment: CrossAxisAlignment.center, 259 children: [ 260 Text( 261 gallery.creator?.displayName ?? '', ··· 263 fontWeight: FontWeight.w600, 264 ), 265 ), 266 - if ((gallery.creator?.displayName ?? '').isNotEmpty && 267 - (gallery.creator?.handle ?? '').isNotEmpty) 268 - const SizedBox(width: 8), 269 - Text( 270 - '@${gallery.creator?.handle ?? ''}', 271 - style: theme.textTheme.bodyMedium?.copyWith( 272 - color: theme.hintColor, 273 ), 274 - ), 275 ], 276 ), 277 ),
··· 1 import 'package:flutter/material.dart'; 2 import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:grain/api.dart'; 4 + import 'package:grain/app_icons.dart'; 5 import 'package:grain/models/gallery_photo.dart'; 6 import 'package:grain/providers/gallery_cache_provider.dart'; 7 import 'package:grain/providers/profile_provider.dart'; 8 import 'package:grain/screens/comments_page.dart'; 9 import 'package:grain/screens/create_gallery_page.dart'; 10 + import 'package:grain/screens/edit_alt_text_sheet.dart'; 11 import 'package:grain/screens/gallery_action_sheet.dart'; 12 import 'package:grain/screens/gallery_edit_photos_sheet.dart'; 13 import 'package:grain/screens/gallery_sort_order_sheet.dart'; ··· 121 actions: [ 122 if (gallery.creator?.did == widget.currentUserDid) 123 IconButton( 124 + icon: const Icon(AppIcons.moreVertical), 125 tooltip: 'Gallery Actions', 126 onPressed: () async { 127 showModalBottomSheet( ··· 139 allPhotos: gallery.items, 140 onSave: (newSelection) { 141 // TODO: Save new selection to backend and refresh gallery 142 + }, 143 + ); 144 + }, 145 + onEditAltText: () { 146 + showEditAltTextSheet( 147 + context, 148 + photos: gallery.items, 149 + onSave: (altTexts) async { 150 + // altTexts: Map<String, String?> (photoUri -> alt) 151 + final altUpdates = altTexts.entries 152 + .map((e) => {'photoUri': e.key, 'alt': e.value}) 153 + .toList(); 154 + await ref 155 + .read(galleryCacheProvider.notifier) 156 + .updatePhotoAltTexts( 157 + galleryUri: gallery.uri, 158 + altUpdates: altUpdates, 159 + ); 160 }, 161 ); 162 }, ··· 168 await ref 169 .read(galleryCacheProvider.notifier) 170 .reorderGalleryItems(galleryUri: gallery.uri, newOrder: newOrder); 171 if (!sheetContext.mounted) return; 172 Navigator.of(sheetContext).pop(); 173 if (!mounted) return; ··· 244 (gallery.creator == null || 245 (gallery.creator!.avatar?.isNotEmpty != true)) 246 ? Icon( 247 + AppIcons.accountCircle, 248 size: 24, 249 color: theme.colorScheme.onSurface.withOpacity(0.4), 250 ) ··· 273 ); 274 } 275 : null, 276 + child: Column( 277 + crossAxisAlignment: CrossAxisAlignment.start, 278 children: [ 279 Text( 280 gallery.creator?.displayName ?? '', ··· 282 fontWeight: FontWeight.w600, 283 ), 284 ), 285 + if ((gallery.creator?.handle ?? '').isNotEmpty) 286 + Text( 287 + '@${gallery.creator?.handle ?? ''}', 288 + style: theme.textTheme.bodyMedium?.copyWith( 289 + color: theme.hintColor, 290 + ), 291 ), 292 ], 293 ), 294 ),
-2
lib/screens/home_page.dart
··· 132 ); 133 } 134 135 - // ...existing code... 136 - 137 @override 138 Widget build(BuildContext context) { 139 final theme = Theme.of(context);
··· 132 ); 133 } 134 135 @override 136 Widget build(BuildContext context) { 137 final theme = Theme.of(context);
+6 -2
lib/screens/library_photos_select_sheet.dart
··· 40 Future<void> _fetchPhotos() async { 41 setState(() => _loading = true); 42 final photos = await apiService.fetchActorPhotos(did: widget.actorDid); 43 if (mounted) { 44 setState(() { 45 - _photos = photos; 46 _loading = false; 47 }); 48 } ··· 139 ], 140 ), 141 ), 142 - // ...existing code... 143 ], 144 ), 145 );
··· 40 Future<void> _fetchPhotos() async { 41 setState(() => _loading = true); 42 final photos = await apiService.fetchActorPhotos(did: widget.actorDid); 43 + // Get gallery items from provider 44 + final galleryItems = ref.read(galleryCacheProvider)[widget.galleryUri]?.items ?? []; 45 + final galleryUris = galleryItems.map((item) => item.uri).toSet(); 46 + // Filter out photos already in gallery 47 + final filteredPhotos = photos.where((photo) => !galleryUris.contains(photo.uri)).toList(); 48 if (mounted) { 49 setState(() { 50 + _photos = filteredPhotos; 51 _loading = false; 52 }); 53 } ··· 144 ], 145 ), 146 ), 147 ], 148 ), 149 );
+51 -46
lib/screens/notifications_page.dart
··· 1 import 'package:flutter/material.dart'; 2 import 'package:grain/api.dart'; 3 import 'package:grain/app_icons.dart'; 4 import 'package:grain/models/notification.dart' as grain; 5 import 'package:grain/screens/gallery_page.dart'; 6 import 'package:grain/screens/profile_page.dart'; 7 import 'package:grain/utils.dart'; 8 import 'package:grain/widgets/gallery_preview.dart'; 9 10 - class NotificationsPage extends StatefulWidget { 11 const NotificationsPage({super.key}); 12 13 @override 14 - State<NotificationsPage> createState() => _NotificationsPageState(); 15 } 16 17 - class _NotificationsPageState extends State<NotificationsPage> { 18 - bool _loading = true; 19 - bool _error = false; 20 - List<grain.Notification> _notifications = []; 21 22 @override 23 void initState() { 24 super.initState(); 25 - _fetchNotifications(); 26 - } 27 - 28 - Future<void> _fetchNotifications() async { 29 - setState(() { 30 - _loading = true; 31 - _error = false; 32 }); 33 - try { 34 - final notifications = await apiService.getNotifications(); 35 - if (!mounted) return; 36 - setState(() { 37 - _notifications = notifications; 38 - _loading = false; 39 - }); 40 - } catch (e) { 41 - if (!mounted) return; 42 - setState(() { 43 - _error = true; 44 - _loading = false; 45 - }); 46 - } 47 } 48 49 - Widget _buildNotificationTile(grain.Notification notification) { 50 final theme = Theme.of(context); 51 final author = notification.author; 52 String message = ''; ··· 235 236 @override 237 Widget build(BuildContext context) { 238 final theme = Theme.of(context); 239 return Scaffold( 240 backgroundColor: theme.scaffoldBackgroundColor, 241 - body: _loading 242 - ? ListView.separated( 243 - itemCount: 6, 244 - separatorBuilder: (context, index) => Divider(height: 1, color: theme.dividerColor), 245 - itemBuilder: (context, index) => _buildSkeletonTile(context), 246 - ) 247 - : _error 248 - ? Center(child: Text('Failed to load notifications.', style: theme.textTheme.bodyMedium)) 249 - : _notifications.isEmpty 250 - ? Center(child: Text('No notifications yet.', style: theme.textTheme.bodyMedium)) 251 - : ListView.separated( 252 - itemCount: _notifications.length, 253 - separatorBuilder: (context, index) => Divider(height: 1, color: theme.dividerColor), 254 - itemBuilder: (context, index) { 255 - final notification = _notifications[index]; 256 - return _buildNotificationTile(notification); 257 - }, 258 - ), 259 ); 260 } 261 }
··· 1 import 'package:flutter/material.dart'; 2 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:grain/api.dart'; 4 import 'package:grain/app_icons.dart'; 5 import 'package:grain/models/notification.dart' as grain; 6 + import 'package:grain/providers/notifications_provider.dart'; 7 import 'package:grain/screens/gallery_page.dart'; 8 import 'package:grain/screens/profile_page.dart'; 9 import 'package:grain/utils.dart'; 10 import 'package:grain/widgets/gallery_preview.dart'; 11 12 + class NotificationsPage extends ConsumerStatefulWidget { 13 const NotificationsPage({super.key}); 14 15 @override 16 + ConsumerState<NotificationsPage> createState() => _NotificationsPageState(); 17 } 18 19 + class _NotificationsPageState extends ConsumerState<NotificationsPage> { 20 + bool _seenCalled = false; 21 22 @override 23 void initState() { 24 super.initState(); 25 + // Only call updateSeen once per visit 26 + WidgetsBinding.instance.addPostFrameCallback((_) { 27 + if (!_seenCalled) { 28 + ref.read(notificationsProvider.notifier).updateSeen(); 29 + _seenCalled = true; 30 + } 31 }); 32 } 33 34 + Widget _buildNotificationTile(BuildContext context, grain.Notification notification) { 35 final theme = Theme.of(context); 36 final author = notification.author; 37 String message = ''; ··· 220 221 @override 222 Widget build(BuildContext context) { 223 + final ref = this.ref; 224 final theme = Theme.of(context); 225 + final notificationsAsync = ref.watch(notificationsProvider); 226 + 227 + final notificationList = notificationsAsync.when( 228 + loading: () => ListView.separated( 229 + itemCount: 6, 230 + separatorBuilder: (context, index) => Divider(height: 1, color: theme.dividerColor), 231 + itemBuilder: (context, index) => _buildSkeletonTile(context), 232 + ), 233 + error: (error, stack) => 234 + Center(child: Text('Failed to load notifications', style: theme.textTheme.bodyMedium)), 235 + data: (notifications) { 236 + if (notifications.isEmpty) { 237 + return Center(child: Text('No notifications yet.', style: theme.textTheme.bodyMedium)); 238 + } else { 239 + return ListView.separated( 240 + itemCount: notifications.length, 241 + separatorBuilder: (context, index) => Divider(height: 1, color: theme.dividerColor), 242 + itemBuilder: (context, index) { 243 + final notification = notifications[index]; 244 + return _buildNotificationTile(context, notification); 245 + }, 246 + ); 247 + } 248 + }, 249 + ); 250 + 251 return Scaffold( 252 backgroundColor: theme.scaffoldBackgroundColor, 253 + body: RefreshIndicator( 254 + onRefresh: () async { 255 + await ref.read(notificationsProvider.notifier).fetch(); 256 + }, 257 + child: notificationList is Center 258 + ? ListView( 259 + physics: const AlwaysScrollableScrollPhysics(), 260 + children: [SizedBox(height: 300), notificationList], 261 + ) 262 + : notificationList, 263 + ), 264 ); 265 } 266 }
+113
lib/utils/exif_utils.dart
···
··· 1 + import 'dart:io'; 2 + 3 + import 'package:exif/exif.dart'; 4 + 5 + import '../app_logger.dart'; 6 + 7 + const List<String> exifTags = [ 8 + 'DateTimeOriginal', 9 + 'ExposureTime', 10 + 'FNumber', 11 + 'Flash', 12 + 'FocalLengthIn35mmFilm', 13 + 'ISOSpeedRatings', 14 + 'LensMake', 15 + 'LensModel', 16 + 'Make', 17 + 'Model', 18 + ]; 19 + 20 + const int scaleFactor = 1000000; 21 + 22 + Future<Map<String, dynamic>?> parseAndNormalizeExif({required File file}) async { 23 + try { 24 + final Map<String, IfdTag> exifData = await readExifFromBytes(await file.readAsBytes()); 25 + appLogger.i('Parsed EXIF data: $exifData'); 26 + 27 + if (exifData.isEmpty) return null; 28 + final Map<String, dynamic> normalized = {}; 29 + 30 + for (final entry in exifData.entries) { 31 + final fullTag = entry.key; 32 + String tag; 33 + if (fullTag.contains(' ')) { 34 + tag = fullTag.split(' ').last.trim(); 35 + } else { 36 + tag = fullTag.trim(); 37 + } 38 + final value = entry.value.printable; 39 + final camelKey = tag.isNotEmpty ? tag[0].toLowerCase() + tag.substring(1) : tag; 40 + switch (tag) { 41 + case 'DateTimeOriginal': 42 + normalized[camelKey] = _exifDateToIso(value); 43 + break; 44 + case 'ExposureTime': 45 + normalized[camelKey] = _parseScaledInt(value); 46 + break; 47 + case 'FNumber': 48 + normalized[camelKey] = _parseScaledInt(value); 49 + break; 50 + case 'Flash': 51 + normalized[camelKey] = value; 52 + break; 53 + case 'FocalLengthIn35mmFilm': 54 + normalized[camelKey] = _parseScaledInt(value); 55 + break; 56 + case 'ISOSpeedRatings': 57 + normalized[camelKey] = _parseInt(value); 58 + break; 59 + case 'LensMake': 60 + normalized[camelKey] = value; 61 + break; 62 + case 'LensModel': 63 + normalized[camelKey] = value; 64 + break; 65 + case 'Make': 66 + normalized[camelKey] = value; 67 + break; 68 + case 'Model': 69 + normalized[camelKey] = value; 70 + break; 71 + } 72 + } 73 + 74 + return normalized; 75 + } catch (e) { 76 + return null; 77 + } 78 + } 79 + 80 + int? _parseScaledInt(String? value) { 81 + if (value == null) return null; 82 + // Handle fraction like "1/40" 83 + if (value.contains('/')) { 84 + final parts = value.split('/'); 85 + if (parts.length == 2) { 86 + final numerator = double.tryParse(parts[0].trim()); 87 + final denominator = double.tryParse(parts[1].trim()); 88 + if (numerator != null && denominator != null && denominator != 0) { 89 + return (numerator / denominator * scaleFactor).round(); 90 + } 91 + } 92 + } 93 + final numVal = double.tryParse(value.replaceAll(RegExp(r'[^0-9.]'), '')); 94 + if (numVal == null) return null; 95 + return (numVal * scaleFactor).round(); 96 + } 97 + 98 + int? _parseInt(String? value) { 99 + if (value == null) return null; 100 + final intVal = int.tryParse(value.replaceAll(RegExp(r'[^0-9]'), '')); 101 + if (intVal == null) return null; 102 + return intVal * scaleFactor; 103 + } 104 + 105 + String? _exifDateToIso(String? exifDate) { 106 + if (exifDate == null) return null; 107 + // EXIF format: yyyy:MM:dd HH:mm:ss 108 + final match = RegExp(r'^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$').firstMatch(exifDate); 109 + if (match == null) return null; 110 + final formatted = 111 + '${match.group(1)}-${match.group(2)}-${match.group(3)}T${match.group(4)}:${match.group(5)}:${match.group(6)}'; 112 + return formatted; 113 + }
+70
lib/websocket_service.dart
···
··· 1 + import 'dart:io'; 2 + 3 + import 'package:grain/app_logger.dart'; 4 + 5 + /// Handles WebSocket connection, reconnection, and message listening. 6 + class WebSocketService { 7 + final String wsUrl; 8 + final String? accessToken; 9 + final void Function(dynamic message) onMessage; 10 + WebSocket? _ws; 11 + bool _shouldReconnect = false; 12 + int _attempt = 0; 13 + static const int _maxRetries = 5; 14 + Duration _delay = const Duration(seconds: 2); 15 + 16 + WebSocketService({required this.wsUrl, required this.accessToken, required this.onMessage}); 17 + 18 + Future<void> connect() async { 19 + _shouldReconnect = true; 20 + _attempt = 0; 21 + _delay = const Duration(seconds: 2); 22 + await _connectInternal(); 23 + } 24 + 25 + Future<void> disconnect() async { 26 + _shouldReconnect = false; 27 + await _ws?.close(); 28 + _ws = null; 29 + } 30 + 31 + Future<void> _connectInternal() async { 32 + if (accessToken == null) { 33 + appLogger.w('No access token for WebSocket connection'); 34 + return; 35 + } 36 + final headers = {'Authorization': 'Bearer $accessToken', 'Content-Type': 'application/json'}; 37 + try { 38 + appLogger.i('Connecting to WebSocket: $wsUrl (attempt ${_attempt + 1})'); 39 + _ws = await WebSocket.connect(wsUrl, headers: headers); 40 + _ws!.listen( 41 + onMessage, 42 + onError: (error) async { 43 + appLogger.w('WebSocket error: $error'); 44 + await _retry(); 45 + }, 46 + onDone: () async { 47 + appLogger.i('WebSocket connection closed'); 48 + await _retry(); 49 + }, 50 + ); 51 + appLogger.i('Connected to WebSocket: $wsUrl'); 52 + } catch (e) { 53 + appLogger.e('Failed to connect to WebSocket: $e'); 54 + await _retry(); 55 + } 56 + } 57 + 58 + Future<void> _retry() async { 59 + if (!_shouldReconnect) return; 60 + if (_attempt < _maxRetries) { 61 + _attempt++; 62 + appLogger.i('Retrying WebSocket connection in ${_delay.inSeconds} seconds...'); 63 + await Future.delayed(_delay); 64 + _delay *= 2; 65 + await _connectInternal(); 66 + } else { 67 + appLogger.e('Max WebSocket retry attempts reached.'); 68 + } 69 + } 70 + }
+180 -98
lib/widgets/bottom_nav_bar.dart
··· 1 import 'package:flutter/material.dart'; 2 import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:font_awesome_flutter/font_awesome_flutter.dart'; ··· 5 import 'package:grain/app_icons.dart'; 6 import 'package:grain/app_theme.dart'; 7 import 'package:grain/models/profile_with_galleries.dart'; 8 import 'package:grain/providers/profile_provider.dart'; 9 import 'package:grain/widgets/app_image.dart'; 10 11 - class BottomNavBar extends ConsumerWidget { 12 final int navIndex; 13 final VoidCallback onHome; 14 final VoidCallback onExplore; ··· 25 }); 26 27 @override 28 - Widget build(BuildContext context, WidgetRef ref) { 29 final did = apiService.currentUser?.did; 30 final asyncProfile = did != null 31 ? ref.watch(profileNotifierProvider(did)) ··· 36 orElse: () => null, 37 ); 38 39 return Container( 40 decoration: BoxDecoration( 41 - color: Theme.of(context).scaffoldBackgroundColor, 42 - border: Border(top: BorderSide(color: Theme.of(context).dividerColor, width: 1)), 43 ), 44 - height: 42 + MediaQuery.of(context).padding.bottom, 45 child: Row( 46 - mainAxisAlignment: MainAxisAlignment.spaceAround, 47 children: [ 48 - Expanded( 49 - child: GestureDetector( 50 - behavior: HitTestBehavior.opaque, 51 - onTap: onHome, 52 - child: SizedBox( 53 - height: 42 + MediaQuery.of(context).padding.bottom, 54 - child: Transform.translate( 55 - offset: const Offset(0, -10), 56 - child: Center( 57 - child: FaIcon( 58 - AppIcons.house, 59 - size: 20, 60 - color: navIndex == 0 61 - ? AppTheme.primaryColor 62 - : Theme.of(context).colorScheme.onSurfaceVariant, 63 - ), 64 - ), 65 - ), 66 - ), 67 ), 68 ), 69 - Expanded( 70 - child: GestureDetector( 71 - behavior: HitTestBehavior.opaque, 72 - onTap: onExplore, 73 - child: SizedBox( 74 - height: 42 + MediaQuery.of(context).padding.bottom, 75 - child: Transform.translate( 76 - offset: const Offset(0, -10), 77 - child: Center( 78 - child: FaIcon( 79 - AppIcons.magnifyingGlass, 80 - size: 20, 81 - color: navIndex == 1 82 - ? AppTheme.primaryColor 83 - : Theme.of(context).colorScheme.onSurfaceVariant, 84 - ), 85 - ), 86 - ), 87 - ), 88 ), 89 ), 90 - Expanded( 91 - child: GestureDetector( 92 - behavior: HitTestBehavior.opaque, 93 - onTap: onNotifications, 94 - child: SizedBox( 95 - height: 42 + MediaQuery.of(context).padding.bottom, 96 - child: Transform.translate( 97 - offset: const Offset(0, -10), 98 - child: Center( 99 - child: FaIcon( 100 - AppIcons.solidBell, 101 - size: 20, 102 - color: navIndex == 2 103 - ? AppTheme.primaryColor 104 - : Theme.of(context).colorScheme.onSurfaceVariant, 105 ), 106 ), 107 - ), 108 - ), 109 ), 110 ), 111 - Expanded( 112 - child: GestureDetector( 113 - behavior: HitTestBehavior.opaque, 114 - onTap: onProfile, 115 - child: SizedBox( 116 - height: 42 + MediaQuery.of(context).padding.bottom, 117 - child: Transform.translate( 118 - offset: const Offset(0, -10), 119 - child: Center( 120 - child: avatarUrl != null && avatarUrl.isNotEmpty 121 - ? Container( 122 - width: 28, 123 - height: 28, 124 - alignment: Alignment.center, 125 - decoration: navIndex == 3 126 - ? BoxDecoration( 127 - shape: BoxShape.circle, 128 - border: Border.all(color: AppTheme.primaryColor, width: 2.2), 129 - ) 130 - : null, 131 - child: ClipOval( 132 - child: AppImage( 133 - url: avatarUrl, 134 - width: 24, 135 - height: 24, 136 - fit: BoxFit.cover, 137 - ), 138 - ), 139 - ) 140 - : FaIcon( 141 - navIndex == 3 ? AppIcons.solidUser : AppIcons.user, 142 - size: 16, 143 - color: navIndex == 3 144 - ? AppTheme.primaryColor 145 - : Theme.of(context).colorScheme.onSurfaceVariant, 146 - ), 147 - ), 148 ), 149 ), 150 ), 151 ), 152 ], ··· 154 ); 155 } 156 }
··· 1 + import 'dart:async'; 2 import 'package:flutter/material.dart'; 3 import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 import 'package:font_awesome_flutter/font_awesome_flutter.dart'; ··· 6 import 'package:grain/app_icons.dart'; 7 import 'package:grain/app_theme.dart'; 8 import 'package:grain/models/profile_with_galleries.dart'; 9 + import 'package:grain/providers/notifications_provider.dart'; 10 import 'package:grain/providers/profile_provider.dart'; 11 import 'package:grain/widgets/app_image.dart'; 12 13 + class BottomNavBar extends ConsumerStatefulWidget { 14 final int navIndex; 15 final VoidCallback onHome; 16 final VoidCallback onExplore; ··· 27 }); 28 29 @override 30 + ConsumerState<BottomNavBar> createState() => _BottomNavBarState(); 31 + } 32 + 33 + class _BottomNavBarState extends ConsumerState<BottomNavBar> { 34 + int _pressedIndex = -1; 35 + 36 + void _onHoldTap(int index, VoidCallback callback) { 37 + setState(() => _pressedIndex = index); 38 + callback(); 39 + Future.delayed(const Duration(milliseconds: 200), () { 40 + setState(() => _pressedIndex = -1); 41 + }); 42 + } 43 + 44 + Widget _buildNavItem({ 45 + required int index, 46 + required Widget icon, 47 + required VoidCallback onHoldComplete, 48 + }) { 49 + return Expanded( 50 + child: _NavItem( 51 + index: index, 52 + isPressed: _pressedIndex == index, 53 + icon: icon, 54 + onHoldComplete: () => _onHoldTap(index, onHoldComplete), 55 + ), 56 + ); 57 + } 58 + 59 + @override 60 + Widget build(BuildContext context) { 61 + final theme = Theme.of(context); 62 final did = apiService.currentUser?.did; 63 final asyncProfile = did != null 64 ? ref.watch(profileNotifierProvider(did)) ··· 69 orElse: () => null, 70 ); 71 72 + final notifications = ref.watch(notificationsProvider); 73 + final unreadCount = notifications.maybeWhen( 74 + data: (list) => list.where((n) => !n.isRead).length, 75 + orElse: () => 0, 76 + ); 77 + 78 return Container( 79 decoration: BoxDecoration( 80 + color: theme.scaffoldBackgroundColor, 81 + border: Border(top: BorderSide(color: theme.dividerColor, width: 1)), 82 ), 83 + height: 56 + MediaQuery.of(context).padding.bottom, 84 + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), 85 child: Row( 86 children: [ 87 + _buildNavItem( 88 + index: 0, 89 + onHoldComplete: widget.onHome, 90 + icon: FaIcon( 91 + AppIcons.house, 92 + size: 20, 93 + color: widget.navIndex == 0 94 + ? AppTheme.primaryColor 95 + : theme.colorScheme.onSurfaceVariant, 96 ), 97 ), 98 + _buildNavItem( 99 + index: 1, 100 + onHoldComplete: widget.onExplore, 101 + icon: FaIcon( 102 + AppIcons.magnifyingGlass, 103 + size: 20, 104 + color: widget.navIndex == 1 105 + ? AppTheme.primaryColor 106 + : theme.colorScheme.onSurfaceVariant, 107 ), 108 ), 109 + _buildNavItem( 110 + index: 2, 111 + onHoldComplete: widget.onNotifications, 112 + icon: Stack( 113 + alignment: Alignment.center, 114 + children: [ 115 + FaIcon( 116 + AppIcons.solidBell, 117 + size: 20, 118 + color: widget.navIndex == 2 119 + ? AppTheme.primaryColor 120 + : theme.colorScheme.onSurfaceVariant, 121 + ), 122 + if (unreadCount > 0) 123 + Positioned( 124 + right: 0, 125 + top: 0, 126 + child: Container( 127 + padding: 128 + const EdgeInsets.symmetric(horizontal: 4, vertical: 1), 129 + decoration: BoxDecoration( 130 + color: theme.colorScheme.primary, 131 + borderRadius: BorderRadius.circular(10), 132 + border: Border.all( 133 + color: theme.scaffoldBackgroundColor, 134 + width: 1, 135 + ), 136 + ), 137 + constraints: 138 + const BoxConstraints(minWidth: 16, minHeight: 16), 139 + child: Text( 140 + unreadCount > 99 ? '99+' : unreadCount.toString(), 141 + style: const TextStyle( 142 + color: Colors.white, 143 + fontSize: 10, 144 + fontWeight: FontWeight.bold, 145 + ), 146 + textAlign: TextAlign.center, 147 + ), 148 ), 149 ), 150 + ], 151 ), 152 ), 153 + _buildNavItem( 154 + index: 3, 155 + onHoldComplete: widget.onProfile, 156 + icon: avatarUrl != null && avatarUrl.isNotEmpty 157 + ? Container( 158 + width: 28, 159 + height: 28, 160 + alignment: Alignment.center, 161 + decoration: widget.navIndex == 3 162 + ? BoxDecoration( 163 + shape: BoxShape.circle, 164 + border: Border.all( 165 + color: AppTheme.primaryColor, width: 2.2), 166 + ) 167 + : null, 168 + child: ClipOval( 169 + child: AppImage( 170 + url: avatarUrl, 171 + width: 24, 172 + height: 24, 173 + fit: BoxFit.cover, 174 ), 175 ), 176 + ) 177 + : FaIcon( 178 + widget.navIndex == 3 179 + ? AppIcons.solidUser 180 + : AppIcons.user, 181 + size: 16, 182 + color: widget.navIndex == 3 183 + ? AppTheme.primaryColor 184 + : theme.colorScheme.onSurfaceVariant, 185 ), 186 ), 187 ], ··· 189 ); 190 } 191 } 192 + 193 + class _NavItem extends StatefulWidget { 194 + final Widget icon; 195 + final VoidCallback onHoldComplete; 196 + final int index; 197 + final bool isPressed; 198 + 199 + const _NavItem({ 200 + required this.icon, 201 + required this.onHoldComplete, 202 + required this.index, 203 + required this.isPressed, 204 + }); 205 + 206 + @override 207 + State<_NavItem> createState() => _NavItemState(); 208 + } 209 + 210 + class _NavItemState extends State<_NavItem> { 211 + bool _pressed = false; 212 + 213 + @override 214 + Widget build(BuildContext context) { 215 + return GestureDetector( 216 + onTapDown: (_) { 217 + setState(() => _pressed = true); 218 + }, 219 + onTapUp: (_) { 220 + Future.delayed(const Duration(milliseconds: 200), () { 221 + setState(() => _pressed = false); 222 + }); 223 + }, 224 + onTapCancel: () { 225 + setState(() => _pressed = false); 226 + }, 227 + onTap: widget.onHoldComplete, 228 + behavior: HitTestBehavior.opaque, 229 + child: Center( 230 + child: AnimatedScale( 231 + scale: _pressed ? 0.85 : 1.0, 232 + duration: const Duration(milliseconds: 150), 233 + child: widget.icon, 234 + ), 235 + ), 236 + ); 237 + } 238 + }
+65 -22
lib/widgets/edit_profile_sheet.dart
··· 61 late TextEditingController _descriptionController; 62 XFile? _selectedAvatar; 63 bool _saving = false; 64 - bool _hasChanged = false; 65 66 @override 67 void initState() { 68 super.initState(); 69 _displayNameController = TextEditingController(text: widget.initialDisplayName ?? ''); 70 _descriptionController = TextEditingController(text: widget.initialDescription ?? ''); 71 _displayNameController.addListener(_onInputChanged); 72 _descriptionController.addListener(_onInputChanged); 73 } 74 75 void _onInputChanged() { 76 - final displayName = _displayNameController.text.trim(); 77 - final initialDisplayName = widget.initialDisplayName ?? ''; 78 - final displayNameChanged = displayName != initialDisplayName; 79 - final descriptionChanged = 80 - _descriptionController.text.trim() != (widget.initialDescription ?? ''); 81 - final avatarChanged = _selectedAvatar != null; 82 - // Only allow Save if displayName is not empty and at least one field changed 83 - final changed = 84 - (displayNameChanged || descriptionChanged || avatarChanged) && displayName.isNotEmpty; 85 - if (_hasChanged != changed) { 86 - setState(() { 87 - _hasChanged = changed; 88 - }); 89 - } 90 } 91 92 @override 93 void dispose() { 94 - _displayNameController.removeListener(_onInputChanged); 95 - _descriptionController.removeListener(_onInputChanged); 96 _displayNameController.dispose(); 97 _descriptionController.dispose(); 98 super.dispose(); ··· 104 if (picked != null) { 105 setState(() { 106 _selectedAvatar = picked; 107 - _onInputChanged(); 108 }); 109 } 110 } ··· 113 Widget build(BuildContext context) { 114 final theme = Theme.of(context); 115 final avatarRadius = 44.0; 116 return CupertinoPageScaffold( 117 backgroundColor: theme.colorScheme.surface, 118 navigationBar: CupertinoNavigationBar( ··· 132 ), 133 trailing: CupertinoButton( 134 padding: EdgeInsets.zero, 135 - onPressed: (!_hasChanged || _saving) 136 ? null 137 : () async { 138 if (widget.onSave != null) { 139 setState(() { 140 _saving = true; ··· 155 Text( 156 'Save', 157 style: TextStyle( 158 - color: (!_hasChanged || _saving) 159 - ? theme.disabledColor 160 - : theme.colorScheme.primary, 161 fontWeight: FontWeight.w600, 162 ), 163 ), ··· 233 controller: _displayNameController, 234 maxLines: 1, 235 ), 236 const SizedBox(height: 12), 237 PlainTextField( 238 label: 'Description', 239 controller: _descriptionController, 240 maxLines: 6, 241 ), 242 ], 243 ),
··· 61 late TextEditingController _descriptionController; 62 XFile? _selectedAvatar; 63 bool _saving = false; 64 + static const int maxDisplayNameGraphemes = 64; 65 + static const int maxDescriptionGraphemes = 256; 66 67 @override 68 void initState() { 69 super.initState(); 70 _displayNameController = TextEditingController(text: widget.initialDisplayName ?? ''); 71 _descriptionController = TextEditingController(text: widget.initialDescription ?? ''); 72 + // No need to track changes 73 _displayNameController.addListener(_onInputChanged); 74 _descriptionController.addListener(_onInputChanged); 75 } 76 77 void _onInputChanged() { 78 + setState(() { 79 + // Trigger rebuild to update character counts 80 + }); 81 } 82 83 @override 84 void dispose() { 85 _displayNameController.dispose(); 86 _descriptionController.dispose(); 87 super.dispose(); ··· 93 if (picked != null) { 94 setState(() { 95 _selectedAvatar = picked; 96 }); 97 } 98 } ··· 101 Widget build(BuildContext context) { 102 final theme = Theme.of(context); 103 final avatarRadius = 44.0; 104 + final displayNameGraphemes = _displayNameController.text.characters.length; 105 + final descriptionGraphemes = _descriptionController.text.characters.length; 106 return CupertinoPageScaffold( 107 backgroundColor: theme.colorScheme.surface, 108 navigationBar: CupertinoNavigationBar( ··· 122 ), 123 trailing: CupertinoButton( 124 padding: EdgeInsets.zero, 125 + onPressed: _saving 126 ? null 127 : () async { 128 + if (displayNameGraphemes > maxDisplayNameGraphemes || 129 + descriptionGraphemes > maxDescriptionGraphemes) { 130 + await showDialog( 131 + context: context, 132 + builder: (context) => AlertDialog( 133 + title: const Text('Character Limit Exceeded'), 134 + content: Text( 135 + displayNameGraphemes > maxDisplayNameGraphemes 136 + ? 'Display Name must be $maxDisplayNameGraphemes characters or fewer.' 137 + : 'Description must be $maxDescriptionGraphemes characters or fewer.', 138 + ), 139 + actions: [ 140 + TextButton( 141 + child: const Text('OK'), 142 + onPressed: () => Navigator.of(context).pop(), 143 + ), 144 + ], 145 + ), 146 + ); 147 + return; 148 + } 149 if (widget.onSave != null) { 150 setState(() { 151 _saving = true; ··· 166 Text( 167 'Save', 168 style: TextStyle( 169 + color: _saving ? theme.disabledColor : theme.colorScheme.primary, 170 fontWeight: FontWeight.w600, 171 ), 172 ), ··· 242 controller: _displayNameController, 243 maxLines: 1, 244 ), 245 + Padding( 246 + padding: const EdgeInsets.only(top: 4), 247 + child: Row( 248 + mainAxisAlignment: MainAxisAlignment.spaceBetween, 249 + children: [ 250 + const SizedBox(), 251 + Text( 252 + '$displayNameGraphemes/$maxDisplayNameGraphemes', 253 + style: theme.textTheme.bodySmall?.copyWith( 254 + color: displayNameGraphemes > maxDisplayNameGraphemes 255 + ? theme.colorScheme.error 256 + : theme.textTheme.bodySmall?.color, 257 + ), 258 + ), 259 + ], 260 + ), 261 + ), 262 const SizedBox(height: 12), 263 PlainTextField( 264 label: 'Description', 265 controller: _descriptionController, 266 maxLines: 6, 267 + ), 268 + Padding( 269 + padding: const EdgeInsets.only(top: 4), 270 + child: Row( 271 + mainAxisAlignment: MainAxisAlignment.spaceBetween, 272 + children: [ 273 + const SizedBox(), 274 + Text( 275 + '$descriptionGraphemes/$maxDescriptionGraphemes', 276 + style: theme.textTheme.bodySmall?.copyWith( 277 + color: descriptionGraphemes > maxDescriptionGraphemes 278 + ? theme.colorScheme.error 279 + : theme.textTheme.bodySmall?.color, 280 + ), 281 + ), 282 + ], 283 + ), 284 ), 285 ], 286 ),
+67 -44
lib/widgets/gallery_photo_view.dart
··· 6 import 'package:grain/widgets/add_comment_button.dart'; 7 import 'package:grain/widgets/add_comment_sheet.dart'; 8 import 'package:grain/widgets/app_image.dart'; 9 10 class GalleryPhotoView extends ConsumerStatefulWidget { 11 final List<GalleryPhoto> photos; ··· 98 top: false, 99 child: Padding( 100 padding: const EdgeInsets.only(top: 8, left: 16, right: 16, bottom: 8), 101 - child: AddCommentButton( 102 - onPressed: () async { 103 - final photo = widget.photos[_currentIndex]; 104 - final creator = widget.gallery?.creator; 105 - final replyTo = { 106 - 'author': creator != null 107 - ? { 108 - 'avatar': creator.avatar, 109 - 'displayName': creator.displayName, 110 - 'handle': creator.handle, 111 - } 112 - : {'avatar': null, 'displayName': '', 'handle': ''}, 113 - 'focus': photo, 114 - 'text': '', 115 - }; 116 - bool commentPosted = false; 117 - await showAddCommentSheet( 118 - context, 119 - gallery: null, 120 - replyTo: replyTo, 121 - initialText: '', 122 - onSubmit: (text) async { 123 - final photo = widget.photos[_currentIndex]; 124 - final gallery = widget.gallery; 125 - final subject = gallery?.uri; 126 - final focus = photo.uri; 127 - if (subject == null || focus == null) { 128 - return; 129 - } 130 - // Use the provider's createComment method 131 - final notifier = ref.read(galleryThreadProvider(subject).notifier); 132 - final success = await notifier.createComment(text: text, focus: focus); 133 - if (success) commentPosted = true; 134 - // Sheet will pop itself 135 - }, 136 - ); 137 - // After sheet closes, notify parent if a comment was posted 138 - if (commentPosted && widget.gallery?.uri != null) { 139 - widget.onClose?.call(); // Remove GalleryPhotoView overlay 140 - widget.onCommentPosted?.call(widget.gallery!.uri); 141 - } 142 - }, 143 - backgroundColor: Colors.grey[900], 144 - foregroundColor: Colors.white, 145 ), 146 ), 147 ),
··· 6 import 'package:grain/widgets/add_comment_button.dart'; 7 import 'package:grain/widgets/add_comment_sheet.dart'; 8 import 'package:grain/widgets/app_image.dart'; 9 + import 'package:grain/widgets/photo_exif_dialog.dart'; 10 11 class GalleryPhotoView extends ConsumerStatefulWidget { 12 final List<GalleryPhoto> photos; ··· 99 top: false, 100 child: Padding( 101 padding: const EdgeInsets.only(top: 8, left: 16, right: 16, bottom: 8), 102 + child: Row( 103 + children: [ 104 + Expanded( 105 + child: AddCommentButton( 106 + onPressed: () async { 107 + final photo = widget.photos[_currentIndex]; 108 + final creator = widget.gallery?.creator; 109 + final replyTo = { 110 + 'author': creator != null 111 + ? { 112 + 'avatar': creator.avatar, 113 + 'displayName': creator.displayName, 114 + 'handle': creator.handle, 115 + } 116 + : {'avatar': null, 'displayName': '', 'handle': ''}, 117 + 'focus': photo, 118 + 'text': '', 119 + }; 120 + bool commentPosted = false; 121 + await showAddCommentSheet( 122 + context, 123 + gallery: null, 124 + replyTo: replyTo, 125 + initialText: '', 126 + onSubmit: (text) async { 127 + final photo = widget.photos[_currentIndex]; 128 + final gallery = widget.gallery; 129 + final subject = gallery?.uri; 130 + final focus = photo.uri; 131 + if (subject == null || focus == null) { 132 + return; 133 + } 134 + // Use the provider's createComment method 135 + final notifier = ref.read( 136 + galleryThreadProvider(subject).notifier, 137 + ); 138 + final success = await notifier.createComment( 139 + text: text, 140 + focus: focus, 141 + ); 142 + if (success) commentPosted = true; 143 + // Sheet will pop itself 144 + }, 145 + ); 146 + // After sheet closes, notify parent if a comment was posted 147 + if (commentPosted && widget.gallery?.uri != null) { 148 + widget.onClose?.call(); // Remove GalleryPhotoView overlay 149 + widget.onCommentPosted?.call(widget.gallery!.uri); 150 + } 151 + }, 152 + backgroundColor: Colors.grey[900], 153 + foregroundColor: Colors.white, 154 + ), 155 + ), 156 + const SizedBox(width: 12), 157 + if (photo.exif != null) 158 + IconButton( 159 + icon: Icon(Icons.camera_alt, color: Colors.white), 160 + onPressed: () { 161 + showDialog( 162 + context: context, 163 + builder: (context) => PhotoExifDialog(exif: photo.exif!), 164 + ); 165 + }, 166 + ), 167 + ], 168 ), 169 ), 170 ),
+187
lib/widgets/photo_exif_dialog.dart
···
··· 1 + import 'package:flutter/material.dart'; 2 + import 'package:grain/models/photo_exif.dart'; 3 + 4 + class PhotoExifDialog extends StatelessWidget { 5 + final PhotoExif exif; 6 + const PhotoExifDialog({super.key, required this.exif}); 7 + 8 + @override 9 + Widget build(BuildContext context) { 10 + return Dialog( 11 + backgroundColor: Colors.black45, 12 + child: Padding( 13 + padding: const EdgeInsets.all(20), 14 + child: SingleChildScrollView( 15 + child: Column( 16 + crossAxisAlignment: CrossAxisAlignment.start, 17 + children: [ 18 + Text( 19 + 'Camera Settings', 20 + style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold), 21 + ), 22 + const SizedBox(height: 16), 23 + if (exif.make != null) 24 + RichText( 25 + text: TextSpan( 26 + children: [ 27 + TextSpan( 28 + text: 'Make: ', 29 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 30 + ), 31 + TextSpan( 32 + text: exif.make!, 33 + style: TextStyle(color: Colors.white), 34 + ), 35 + ], 36 + ), 37 + ), 38 + if (exif.model != null) 39 + RichText( 40 + text: TextSpan( 41 + children: [ 42 + TextSpan( 43 + text: 'Model: ', 44 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 45 + ), 46 + TextSpan( 47 + text: exif.model!, 48 + style: TextStyle(color: Colors.white), 49 + ), 50 + ], 51 + ), 52 + ), 53 + if (exif.lensMake != null) 54 + RichText( 55 + text: TextSpan( 56 + children: [ 57 + TextSpan( 58 + text: 'Lens Make: ', 59 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 60 + ), 61 + TextSpan( 62 + text: exif.lensMake!, 63 + style: TextStyle(color: Colors.white), 64 + ), 65 + ], 66 + ), 67 + ), 68 + if (exif.lensModel != null) 69 + RichText( 70 + text: TextSpan( 71 + children: [ 72 + TextSpan( 73 + text: 'Lens Model: ', 74 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 75 + ), 76 + TextSpan( 77 + text: exif.lensModel!, 78 + style: TextStyle(color: Colors.white), 79 + ), 80 + ], 81 + ), 82 + ), 83 + if (exif.fNumber != null) 84 + RichText( 85 + text: TextSpan( 86 + children: [ 87 + TextSpan( 88 + text: 'F Number: ', 89 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 90 + ), 91 + TextSpan( 92 + text: exif.fNumber!, 93 + style: TextStyle(color: Colors.white), 94 + ), 95 + ], 96 + ), 97 + ), 98 + if (exif.focalLengthIn35mmFormat != null) 99 + RichText( 100 + text: TextSpan( 101 + children: [ 102 + TextSpan( 103 + text: 'Focal Length (35mm): ', 104 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 105 + ), 106 + TextSpan( 107 + text: exif.focalLengthIn35mmFormat!, 108 + style: TextStyle(color: Colors.white), 109 + ), 110 + ], 111 + ), 112 + ), 113 + if (exif.exposureTime != null) 114 + RichText( 115 + text: TextSpan( 116 + children: [ 117 + TextSpan( 118 + text: 'Exposure Time: ', 119 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 120 + ), 121 + TextSpan( 122 + text: exif.exposureTime!, 123 + style: TextStyle(color: Colors.white), 124 + ), 125 + ], 126 + ), 127 + ), 128 + if (exif.iSO != null) 129 + RichText( 130 + text: TextSpan( 131 + children: [ 132 + TextSpan( 133 + text: 'ISO: ', 134 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 135 + ), 136 + TextSpan( 137 + text: exif.iSO.toString(), 138 + style: TextStyle(color: Colors.white), 139 + ), 140 + ], 141 + ), 142 + ), 143 + if (exif.flash != null) 144 + RichText( 145 + text: TextSpan( 146 + children: [ 147 + TextSpan( 148 + text: 'Flash: ', 149 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 150 + ), 151 + TextSpan( 152 + text: exif.flash!, 153 + style: TextStyle(color: Colors.white), 154 + ), 155 + ], 156 + ), 157 + ), 158 + if (exif.dateTimeOriginal != null) 159 + RichText( 160 + text: TextSpan( 161 + children: [ 162 + TextSpan( 163 + text: 'DateTime Original: ', 164 + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), 165 + ), 166 + TextSpan( 167 + text: exif.dateTimeOriginal!, 168 + style: TextStyle(color: Colors.white), 169 + ), 170 + ], 171 + ), 172 + ), 173 + const SizedBox(height: 20), 174 + Align( 175 + alignment: Alignment.centerRight, 176 + child: TextButton( 177 + onPressed: () => Navigator.of(context).pop(), 178 + child: Text('Close', style: TextStyle(color: Colors.white)), 179 + ), 180 + ), 181 + ], 182 + ), 183 + ), 184 + ), 185 + ); 186 + } 187 + }
+6 -1
lib/widgets/plain_text_field.dart
··· 8 final TextInputType? keyboardType; 9 final String? hintText; 10 final void Function(String)? onChanged; 11 12 const PlainTextField({ 13 super.key, ··· 18 this.keyboardType, 19 this.hintText, 20 this.onChanged, 21 }); 22 23 @override ··· 45 final isFocused = Focus.of(context).hasFocus; 46 return Stack( 47 children: [ 48 - // TextField with internal padding 49 TextField( 50 controller: controller, 51 maxLines: maxLines, ··· 59 border: InputBorder.none, 60 contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), 61 isDense: true, 62 ), 63 ), 64 // Border overlay
··· 8 final TextInputType? keyboardType; 9 final String? hintText; 10 final void Function(String)? onChanged; 11 + final Widget? prefixIcon; 12 + final Widget? suffixIcon; 13 14 const PlainTextField({ 15 super.key, ··· 20 this.keyboardType, 21 this.hintText, 22 this.onChanged, 23 + this.prefixIcon, 24 + this.suffixIcon, 25 }); 26 27 @override ··· 49 final isFocused = Focus.of(context).hasFocus; 50 return Stack( 51 children: [ 52 TextField( 53 controller: controller, 54 maxLines: maxLines, ··· 62 border: InputBorder.none, 63 contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), 64 isDense: true, 65 + prefixIcon: prefixIcon, 66 + suffixIcon: suffixIcon, 67 ), 68 ), 69 // Border overlay
+44 -23
lib/widgets/timeline_item.dart
··· 8 import 'package:grain/widgets/faceted_text.dart'; 9 import 'package:grain/widgets/gallery_action_buttons.dart'; 10 import 'package:grain/widgets/gallery_preview.dart'; 11 12 import '../providers/gallery_cache_provider.dart'; 13 import '../screens/gallery_page.dart'; ··· 73 mainAxisAlignment: MainAxisAlignment.spaceBetween, 74 children: [ 75 Flexible( 76 - child: Text.rich( 77 - TextSpan( 78 - children: [ 79 - if (actor?.displayName?.isNotEmpty ?? false) 80 - TextSpan( 81 - text: actor!.displayName ?? '', 82 - style: theme.textTheme.titleMedium?.copyWith( 83 - fontWeight: FontWeight.w600, 84 - fontSize: 16, 85 ), 86 - ), 87 - if (actor != null && actor.handle.isNotEmpty) 88 - TextSpan( 89 - text: (actor.displayName?.isNotEmpty ?? false) 90 - ? ' @${actor.handle}' 91 - : '@${actor.handle}', 92 - style: theme.textTheme.bodySmall?.copyWith( 93 - fontSize: 14, 94 - color: theme.colorScheme.onSurfaceVariant, 95 - fontWeight: FontWeight.normal, 96 ), 97 - ), 98 - ], 99 ), 100 - overflow: TextOverflow.ellipsis, 101 - maxLines: 1, 102 ), 103 ), 104 Text( ··· 159 context, 160 MaterialPageRoute(builder: (_) => HashtagPage(hashtag: tag)), 161 ), 162 ), 163 ), 164 const SizedBox(height: 8),
··· 8 import 'package:grain/widgets/faceted_text.dart'; 9 import 'package:grain/widgets/gallery_action_buttons.dart'; 10 import 'package:grain/widgets/gallery_preview.dart'; 11 + import 'package:url_launcher/url_launcher.dart'; 12 13 import '../providers/gallery_cache_provider.dart'; 14 import '../screens/gallery_page.dart'; ··· 74 mainAxisAlignment: MainAxisAlignment.spaceBetween, 75 children: [ 76 Flexible( 77 + child: GestureDetector( 78 + onTap: 79 + onProfileTap ?? 80 + () { 81 + if (actor?.did != null) { 82 + Navigator.of(context).push( 83 + MaterialPageRoute( 84 + builder: (context) => 85 + ProfilePage(did: actor!.did, showAppBar: true), 86 + ), 87 + ); 88 + } 89 + }, 90 + child: Text.rich( 91 + TextSpan( 92 + children: [ 93 + if (actor?.displayName?.isNotEmpty ?? false) 94 + TextSpan( 95 + text: actor!.displayName ?? '', 96 + style: theme.textTheme.titleMedium?.copyWith( 97 + fontWeight: FontWeight.w600, 98 + fontSize: 16, 99 + ), 100 ), 101 + if (actor != null && actor.handle.isNotEmpty) 102 + TextSpan( 103 + text: (actor.displayName?.isNotEmpty ?? false) 104 + ? ' @${actor.handle}' 105 + : '@${actor.handle}', 106 + style: theme.textTheme.bodySmall?.copyWith( 107 + fontSize: 14, 108 + color: theme.colorScheme.onSurfaceVariant, 109 + fontWeight: FontWeight.normal, 110 + ), 111 ), 112 + ], 113 + ), 114 + overflow: TextOverflow.ellipsis, 115 + maxLines: 1, 116 ), 117 ), 118 ), 119 Text( ··· 174 context, 175 MaterialPageRoute(builder: (_) => HashtagPage(hashtag: tag)), 176 ), 177 + onLinkTap: (url) async { 178 + final uri = Uri.parse(url); 179 + if (!await launchUrl(uri)) { 180 + throw Exception('Could not launch $url'); 181 + } 182 + }, 183 ), 184 ), 185 const SizedBox(height: 8),
+104
lib/widgets/upload_progress_overlay.dart
···
··· 1 + import 'dart:io'; 2 + 3 + import 'package:flutter/material.dart'; 4 + 5 + import '../screens/create_gallery_page.dart'; 6 + 7 + class UploadProgressOverlay extends StatelessWidget { 8 + final List<GalleryImage> images; 9 + final int currentIndex; 10 + final double progress; // 0.0 - 1.0 11 + final bool visible; 12 + 13 + const UploadProgressOverlay({ 14 + super.key, 15 + required this.images, 16 + required this.currentIndex, 17 + required this.progress, 18 + this.visible = false, 19 + }); 20 + 21 + @override 22 + Widget build(BuildContext context) { 23 + if (!visible) return const SizedBox.shrink(); 24 + final theme = Theme.of(context); 25 + 26 + // Get the current image being uploaded 27 + final currentImage = currentIndex < images.length ? images[currentIndex] : null; 28 + 29 + // Calculate overall progress: completed images + current image's progress 30 + double overallProgress = 0.0; 31 + if (images.isNotEmpty) { 32 + overallProgress = (currentIndex + progress) / images.length; 33 + } 34 + 35 + return Material( 36 + color: Colors.transparent, 37 + child: Stack( 38 + children: [ 39 + Positioned.fill(child: Container(color: Colors.black.withOpacity(0.9))), 40 + Center( 41 + child: Padding( 42 + padding: const EdgeInsets.all(32), 43 + child: Column( 44 + mainAxisSize: MainAxisSize.min, 45 + mainAxisAlignment: MainAxisAlignment.center, 46 + children: [ 47 + Row( 48 + mainAxisSize: MainAxisSize.min, 49 + children: [ 50 + SizedBox( 51 + width: 24, 52 + height: 24, 53 + child: CircularProgressIndicator( 54 + strokeWidth: 2.5, 55 + valueColor: AlwaysStoppedAnimation<Color>(Colors.white), 56 + ), 57 + ), 58 + const SizedBox(width: 12), 59 + Text( 60 + 'Uploading photos...', 61 + style: theme.textTheme.titleMedium?.copyWith(color: Colors.white), 62 + ), 63 + ], 64 + ), 65 + const SizedBox(height: 16), 66 + 67 + // Show current image at true aspect ratio 68 + if (currentImage != null) 69 + Container( 70 + constraints: const BoxConstraints(maxWidth: 300, maxHeight: 300), 71 + child: Image.file( 72 + File(currentImage.file.path), 73 + fit: BoxFit.contain, // Maintain aspect ratio 74 + ), 75 + ), 76 + 77 + const SizedBox(height: 16), 78 + 79 + // Progress indicator (overall progress) 80 + SizedBox( 81 + width: 300, 82 + child: LinearProgressIndicator( 83 + value: overallProgress, 84 + backgroundColor: theme.colorScheme.surfaceContainerHighest.withOpacity(0.5), 85 + valueColor: AlwaysStoppedAnimation<Color>(theme.colorScheme.primary), 86 + ), 87 + ), 88 + 89 + const SizedBox(height: 8), 90 + 91 + // Position counter and progress percentage 92 + Text( 93 + '${currentIndex + 1} of ${images.length} โ€ข ${(overallProgress * 100).toInt()}%', 94 + style: theme.textTheme.bodyMedium?.copyWith(color: Colors.white70), 95 + ), 96 + ], 97 + ), 98 + ), 99 + ), 100 + ], 101 + ), 102 + ); 103 + } 104 + }
+2
macos/Flutter/GeneratedPluginRegistrant.swift
··· 12 import package_info_plus 13 import path_provider_foundation 14 import share_plus 15 import sqflite_darwin 16 import url_launcher_macos 17 import window_to_front ··· 24 FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 25 PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 26 SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 27 SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 28 UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 29 WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
··· 12 import package_info_plus 13 import path_provider_foundation 14 import share_plus 15 + import shared_preferences_foundation 16 import sqflite_darwin 17 import url_launcher_macos 18 import window_to_front ··· 25 FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 26 PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 27 SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 28 + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 29 SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 30 UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 31 WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
+72 -8
pubspec.lock
··· 149 dependency: transitive 150 description: 151 name: built_value 152 - sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" 153 url: "https://pub.dev" 154 source: hosted 155 - version: "8.10.1" 156 cached_network_image: 157 dependency: "direct main" 158 description: ··· 309 dependency: transitive 310 description: 311 name: dart_style 312 - sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" 313 url: "https://pub.dev" 314 source: hosted 315 - version: "3.1.0" 316 desktop_webview_window: 317 dependency: transitive 318 description: ··· 321 url: "https://pub.dev" 322 source: hosted 323 version: "0.2.3" 324 fake_async: 325 dependency: transitive 326 description: ··· 612 dependency: transitive 613 description: 614 name: image_picker_android 615 - sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" 616 url: "https://pub.dev" 617 source: hosted 618 - version: "0.8.12+23" 619 image_picker_for_web: 620 dependency: transitive 621 description: ··· 740 dependency: "direct main" 741 description: 742 name: logger 743 - sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999" 744 url: "https://pub.dev" 745 source: hosted 746 - version: "2.6.0" 747 logging: 748 dependency: transitive 749 description: ··· 1024 url: "https://pub.dev" 1025 source: hosted 1026 version: "6.0.0" 1027 shelf: 1028 dependency: transitive 1029 description:
··· 149 dependency: transitive 150 description: 151 name: built_value 152 + sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62" 153 url: "https://pub.dev" 154 source: hosted 155 + version: "8.11.0" 156 cached_network_image: 157 dependency: "direct main" 158 description: ··· 309 dependency: transitive 310 description: 311 name: dart_style 312 + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" 313 url: "https://pub.dev" 314 source: hosted 315 + version: "3.1.1" 316 desktop_webview_window: 317 dependency: transitive 318 description: ··· 321 url: "https://pub.dev" 322 source: hosted 323 version: "0.2.3" 324 + exif: 325 + dependency: "direct main" 326 + description: 327 + name: exif 328 + sha256: a7980fdb3b7ffcd0b035e5b8a5e1eef7cadfe90ea6a4e85ebb62f87b96c7a172 329 + url: "https://pub.dev" 330 + source: hosted 331 + version: "3.3.0" 332 fake_async: 333 dependency: transitive 334 description: ··· 620 dependency: transitive 621 description: 622 name: image_picker_android 623 + sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d" 624 url: "https://pub.dev" 625 source: hosted 626 + version: "0.8.12+24" 627 image_picker_for_web: 628 dependency: transitive 629 description: ··· 748 dependency: "direct main" 749 description: 750 name: logger 751 + sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" 752 url: "https://pub.dev" 753 source: hosted 754 + version: "2.6.1" 755 logging: 756 dependency: transitive 757 description: ··· 1032 url: "https://pub.dev" 1033 source: hosted 1034 version: "6.0.0" 1035 + shared_preferences: 1036 + dependency: "direct main" 1037 + description: 1038 + name: shared_preferences 1039 + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" 1040 + url: "https://pub.dev" 1041 + source: hosted 1042 + version: "2.5.3" 1043 + shared_preferences_android: 1044 + dependency: transitive 1045 + description: 1046 + name: shared_preferences_android 1047 + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" 1048 + url: "https://pub.dev" 1049 + source: hosted 1050 + version: "2.4.10" 1051 + shared_preferences_foundation: 1052 + dependency: transitive 1053 + description: 1054 + name: shared_preferences_foundation 1055 + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" 1056 + url: "https://pub.dev" 1057 + source: hosted 1058 + version: "2.5.4" 1059 + shared_preferences_linux: 1060 + dependency: transitive 1061 + description: 1062 + name: shared_preferences_linux 1063 + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" 1064 + url: "https://pub.dev" 1065 + source: hosted 1066 + version: "2.4.1" 1067 + shared_preferences_platform_interface: 1068 + dependency: transitive 1069 + description: 1070 + name: shared_preferences_platform_interface 1071 + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" 1072 + url: "https://pub.dev" 1073 + source: hosted 1074 + version: "2.4.1" 1075 + shared_preferences_web: 1076 + dependency: transitive 1077 + description: 1078 + name: shared_preferences_web 1079 + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 1080 + url: "https://pub.dev" 1081 + source: hosted 1082 + version: "2.4.3" 1083 + shared_preferences_windows: 1084 + dependency: transitive 1085 + description: 1086 + name: shared_preferences_windows 1087 + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" 1088 + url: "https://pub.dev" 1089 + source: hosted 1090 + version: "2.4.1" 1091 shelf: 1092 dependency: transitive 1093 description:
+4 -2
pubspec.yaml
··· 1 name: grain 2 - description: "A new Flutter project." 3 # The following line prevents the package from being accidentally published to 4 # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 publish_to: "none" # Remove this line if you wish to publish to pub.dev ··· 16 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 # In Windows, build-name is used as the major, minor, and patch parts 18 # of the product and file versions while build-number is used as the build suffix. 19 - version: 1.0.0+12 20 21 environment: 22 sdk: ^3.8.1 ··· 59 json_annotation: ^4.9.0 60 url_launcher: ^6.3.1 61 reorderables: ^0.6.0 62 63 dependency_overrides: 64 analyzer: 7.3.0
··· 1 name: grain 2 + description: "Grain Social Mobile App" 3 # The following line prevents the package from being accidentally published to 4 # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 publish_to: "none" # Remove this line if you wish to publish to pub.dev ··· 16 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 # In Windows, build-name is used as the major, minor, and patch parts 18 # of the product and file versions while build-number is used as the build suffix. 19 + version: 1.0.0+20 20 21 environment: 22 sdk: ^3.8.1 ··· 59 json_annotation: ^4.9.0 60 url_launcher: ^6.3.1 61 reorderables: ^0.6.0 62 + exif: ^3.3.0 63 + shared_preferences: ^2.5.3 64 65 dependency_overrides: 66 analyzer: 7.3.0