Compare changes

Choose any two refs to compare.

Changed files
+15090 -1845
.github
instructions
.vscode
lexicons
lib
models
procedures
providers
screens
utils
widgets
+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 2 "editor.codeActionsOnSave": { 3 3 "source.organizeImports": "always" 4 4 }, 5 - "dart.lineLength": 100 5 + "dart.lineLength": 100, 6 + "dart.flutterHotReloadOnSave": "all" 6 7 }
+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 + }
+317 -696
lib/api.dart
··· 1 1 import 'dart:convert'; 2 2 import 'dart:io'; 3 3 4 - import 'package:at_uri/at_uri.dart'; 5 4 import 'package:grain/app_logger.dart'; 6 - import 'package:grain/dpop_client.dart'; 7 5 import 'package:grain/main.dart'; 8 6 import 'package:grain/models/session.dart'; 9 - import 'package:grain/photo_manip.dart'; 10 7 import 'package:http/http.dart' as http; 11 8 import 'package:mime/mime.dart'; 12 9 ··· 14 11 import 'models/followers_result.dart'; 15 12 import 'models/follows_result.dart'; 16 13 import 'models/gallery.dart'; 17 - import 'models/gallery_item.dart'; 18 14 import 'models/gallery_photo.dart'; 19 15 import 'models/gallery_thread.dart'; 20 16 import 'models/notification.dart' as grain; 17 + import 'models/procedures/procedures.dart'; 21 18 import 'models/profile.dart'; 22 19 23 20 class ApiService { ··· 27 24 28 25 String get _apiUrl => AppConfig.apiUrl; 29 26 30 - Future<Session?> fetchSession([String? initialToken]) async { 31 - String? token = initialToken; 32 - if (token == null) { 33 - final session = await auth.getValidSession(); 34 - token = session?.token; 35 - if (token == null) return null; 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; 36 46 } 37 - 38 - final response = await http.get( 39 - Uri.parse('$_apiUrl/oauth/session'), 40 - headers: {'Authorization': 'Bearer $token', 'Content-Type': 'application/json'}, 41 - ); 42 - 43 - if (response.statusCode != 200) { 44 - throw Exception('Failed to fetch session'); 45 - } 46 - 47 - return Session.fromJson(jsonDecode(response.body)); 48 47 } 49 48 50 - Future<bool> revokeSession() async { 51 - final session = await auth.getValidSession(); 52 - final token = session?.token; 53 - if (token == null) { 54 - appLogger.w('No access token for revokeSession'); 55 - return false; 56 - } 57 - final url = Uri.parse('$_apiUrl/oauth/revoke'); 58 - final headers = {'Authorization': 'Bearer $token', 'Content-Type': 'application/json'}; 49 + Future<bool> revokeSession(Session session) async { 50 + final url = Uri.parse('$_apiUrl/api/token/revoke'); 51 + final headers = {'Content-Type': 'application/json'}; 59 52 try { 60 - final response = await http.post(url, headers: headers); 53 + final response = await http.post( 54 + url, 55 + headers: headers, 56 + body: jsonEncode({'refreshToken': session.refreshToken}), 57 + ); 61 58 if (response.statusCode == 200) { 62 59 appLogger.i('Session revoked successfully'); 63 60 return true; ··· 74 71 Future<Profile?> fetchCurrentUser() async { 75 72 final session = await auth.getValidSession(); 76 73 77 - if (session == null || session.session.subject.isEmpty) { 74 + if (session == null || session.did.isEmpty) { 78 75 return null; 79 76 } 80 77 81 - final user = await fetchProfile(did: session.session.subject); 78 + final user = await fetchProfile(did: session.did); 82 79 83 80 currentUser = user; 84 81 ··· 289 286 return (json['items'] as List<dynamic>?)?.map((item) => Gallery.fromJson(item)).toList() ?? []; 290 287 } 291 288 292 - Future<String?> createGallery({ 293 - required String title, 294 - required String description, 295 - List<Map<String, dynamic>>? facets, 289 + /// Fetch followers for a given actor DID 290 + Future<FollowersResult> getFollowers({ 291 + required String actor, 292 + String? cursor, 293 + int limit = 50, 296 294 }) async { 297 - final session = await auth.getValidSession(); 298 - if (session == null) { 299 - appLogger.w('No valid session for createGallery'); 300 - return null; 301 - } 302 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 303 - final issuer = session.session.issuer; 304 - final did = session.session.subject; 305 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 306 - final record = { 307 - 'collection': 'social.grain.gallery', 308 - 'repo': did, 309 - 'record': { 310 - 'title': title, 311 - 'description': description, 312 - if (facets != null) 'facets': facets, 313 - 'updatedAt': DateTime.now().toUtc().toIso8601String(), 314 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 315 - }, 316 - }; 317 - appLogger.i('Creating gallery: $record'); 318 - final response = await dpopClient.send( 319 - method: 'POST', 320 - url: url, 321 - accessToken: session.session.accessToken, 322 - headers: {'Content-Type': 'application/json'}, 323 - body: jsonEncode(record), 295 + final uri = Uri.parse( 296 + '$_apiUrl/xrpc/social.grain.graph.getFollowers?actor=$actor&limit=$limit${cursor != null ? '&cursor=$cursor' : ''}', 324 297 ); 325 - if (response.statusCode != 200 && response.statusCode != 201) { 326 - appLogger.w('Failed to create gallery: \\${response.statusCode} \\${response.body}'); 327 - throw Exception('Failed to create gallery: \\${response.statusCode}'); 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}'); 328 301 } 329 - final result = jsonDecode(response.body) as Map<String, dynamic>; 330 - appLogger.i('Created gallery result: $result'); 331 - final uri = result['uri'] as String?; 332 - return uri; 302 + final json = jsonDecode(response.body); 303 + return FollowersResult.fromJson(json); 333 304 } 334 305 335 - /// Polls the gallery until the number of items matches [expectedCount] or timeout. 336 - /// Returns the Gallery if successful, or null if timeout. 337 - Future<Gallery?> pollGalleryItems({ 338 - required String galleryUri, 339 - required int expectedCount, 340 - Duration pollDelay = const Duration(seconds: 2), 341 - int maxAttempts = 20, 342 - }) async { 343 - int attempts = 0; 344 - Gallery? gallery; 345 - while (attempts < maxAttempts) { 346 - gallery = await getGallery(uri: galleryUri); 347 - if (gallery != null && gallery.items.length == expectedCount) { 348 - appLogger.i('Gallery $galleryUri has expected number of items: $expectedCount'); 349 - return gallery; 350 - } 351 - await Future.delayed(pollDelay); 352 - attempts++; 353 - } 354 - appLogger.w( 355 - 'Gallery $galleryUri did not reach expected items count ($expectedCount) after polling.', 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' : ''}', 356 310 ); 357 - return null; 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); 358 317 } 359 318 360 - /// Polls the gallery thread until the number of comments matches [expectedCount] or timeout. 361 - /// Returns the thread map if successful, or null if timeout. 362 - Future<GalleryThread?> pollGalleryThreadComments({ 363 - required String galleryUri, 364 - required int expectedCount, 365 - Duration pollDelay = const Duration(seconds: 2), 366 - int maxAttempts = 20, 367 - }) async { 368 - int attempts = 0; 369 - GalleryThread? thread; 370 - while (attempts < maxAttempts) { 371 - thread = await getGalleryThread(uri: galleryUri); 372 - if (thread != null && thread.comments.length == expectedCount) { 373 - appLogger.i('Gallery thread $galleryUri has expected number of comments: $expectedCount'); 374 - return thread; 375 - } 376 - await Future.delayed(pollDelay); 377 - attempts++; 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'); 378 326 } 379 - appLogger.w( 380 - 'Gallery thread $galleryUri did not reach expected comments count ($expectedCount) after polling.', 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()), 381 332 ); 382 - return null; 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); 383 338 } 384 339 385 - /// Uploads a blob (file) to the atproto uploadBlob endpoint using DPoP authentication. 386 - /// Returns the blob reference map on success, or null on failure. 387 - Future<Map<String, dynamic>?> uploadBlob(File file) async { 340 + Future<UpdateAvatarResponse> updateAvatar({required File avatarFile}) async { 388 341 final session = await auth.getValidSession(); 389 - if (session == null) { 390 - appLogger.w('No valid session for uploadBlob'); 391 - return null; 342 + final token = session?.token; 343 + if (token == null) { 344 + throw Exception('No access token for updateAvatar'); 392 345 } 393 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 394 - final issuer = session.session.issuer; 395 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.uploadBlob'); 396 - 397 - // Detect MIME type, fallback to application/octet-stream if unknown 398 - String? mimeType = lookupMimeType(file.path); 346 + final uri = Uri.parse('$_apiUrl/xrpc/social.grain.actor.updateAvatar'); 347 + String? mimeType = lookupMimeType(avatarFile.path); 399 348 final contentType = mimeType ?? 'application/octet-stream'; 400 - 401 - appLogger.i('Uploading blob: ${file.path} (MIME: $mimeType)'); 402 - 403 - final bytes = await file.readAsBytes(); 404 - 405 - final response = await dpopClient.send( 406 - method: 'POST', 407 - url: url, 408 - accessToken: session.session.accessToken, 409 - headers: {'Content-Type': contentType}, 349 + final bytes = await avatarFile.readAsBytes(); 350 + final response = await http.post( 351 + uri, 352 + headers: {'Authorization': "Bearer $token", 'Content-Type': contentType}, 410 353 body: bytes, 411 354 ); 412 - 413 - if (response.statusCode != 200 && response.statusCode != 201) { 414 - appLogger.w( 415 - 'Failed to upload blob: \\${response.statusCode} \\${response.body} (File: \\${file.path}, MIME: \\${mimeType})', 416 - ); 417 - return null; 355 + if (response.statusCode != 200) { 356 + throw Exception('Failed to update avatar: ${response.statusCode} ${response.body}'); 418 357 } 419 - 420 - try { 421 - final result = jsonDecode(response.body) as Map<String, dynamic>; 422 - appLogger.i('Uploaded blob result: $result'); 423 - return result; 424 - } catch (e, st) { 425 - appLogger.e('Failed to parse uploadBlob response: $e', stackTrace: st); 426 - return null; 427 - } 358 + final json = jsonDecode(response.body); 359 + return UpdateAvatarResponse.fromJson(json); 428 360 } 429 361 430 - Future<String?> createPhoto({ 431 - required Map<String, dynamic> blob, 432 - required int width, 433 - required int height, 434 - String alt = '', 435 - }) async { 362 + Future<ApplySortResponse> applySort({required ApplySortRequest request}) async { 436 363 final session = await auth.getValidSession(); 437 - if (session == null) { 438 - appLogger.w('No valid session for createPhotoRecord'); 439 - return null; 364 + final token = session?.token; 365 + if (token == null) { 366 + throw Exception('No access token for applySort'); 440 367 } 441 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 442 - final issuer = session.session.issuer; 443 - final did = session.session.subject; 444 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 445 - final record = { 446 - 'collection': 'social.grain.photo', 447 - 'repo': did, 448 - 'record': { 449 - 'photo': blob['blob'], 450 - 'aspectRatio': {'width': width, 'height': height}, 451 - 'alt': "", 452 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 453 - }, 454 - }; 455 - appLogger.i('Creating photo record: $record'); 456 - final response = await dpopClient.send( 457 - method: 'POST', 458 - url: url, 459 - accessToken: session.session.accessToken, 460 - headers: {'Content-Type': 'application/json'}, 461 - body: jsonEncode(record), 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()), 462 373 ); 463 - if (response.statusCode != 200 && response.statusCode != 201) { 464 - appLogger.w('Failed to create photo record: \\${response.statusCode} \\${response.body}'); 465 - return null; 374 + if (response.statusCode != 200) { 375 + throw Exception('Failed to apply sort: ${response.statusCode} ${response.body}'); 466 376 } 467 - final result = jsonDecode(response.body) as Map<String, dynamic>; 468 - appLogger.i('Created photo record result: $result'); 469 - return result['uri'] as String?; 377 + final json = jsonDecode(response.body); 378 + return ApplySortResponse.fromJson(json); 470 379 } 471 380 472 - Future<String?> createGalleryItem({ 473 - required String galleryUri, 474 - required String photoUri, 475 - required int position, 476 - }) async { 381 + Future<ApplyAltsResponse> applyAlts({required ApplyAltsRequest request}) async { 477 382 final session = await auth.getValidSession(); 478 - if (session == null) { 479 - appLogger.w('No valid session for createGalleryItem'); 480 - return null; 383 + final token = session?.token; 384 + if (token == null) { 385 + throw Exception('No access token for applyAlts'); 481 386 } 482 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 483 - final issuer = session.session.issuer; 484 - final did = session.session.subject; 485 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 486 - final record = { 487 - 'collection': 'social.grain.gallery.item', 488 - 'repo': did, 489 - 'record': { 490 - 'gallery': galleryUri, 491 - 'item': photoUri, 492 - 'position': position, 493 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 494 - }, 495 - }; 496 - appLogger.i('Creating gallery item: $record'); 497 - final response = await dpopClient.send( 498 - method: 'POST', 499 - url: url, 500 - accessToken: session.session.accessToken, 501 - headers: {'Content-Type': 'application/json'}, 502 - body: jsonEncode(record), 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()), 503 392 ); 504 - if (response.statusCode != 200 && response.statusCode != 201) { 505 - appLogger.w('Failed to create gallery item: \\${response.statusCode} \\${response.body}'); 506 - return null; 393 + if (response.statusCode != 200) { 394 + throw Exception('Failed to apply alts: ${response.statusCode} ${response.body}'); 507 395 } 508 - final result = jsonDecode(response.body) as Map<String, dynamic>; 509 - appLogger.i('Created gallery item result: $result'); 510 - return result['uri'] as String?; 396 + final json = jsonDecode(response.body); 397 + return ApplyAltsResponse.fromJson(json); 511 398 } 512 399 513 - Future<String?> createComment({ 514 - required String text, 515 - List<Map<String, dynamic>>? facets, 516 - required String subject, 517 - String? focus, // Now a String (photo URI) 518 - String? replyTo, 519 - }) async { 400 + Future<CreateExifResponse> createExif({required CreateExifRequest request}) async { 520 401 final session = await auth.getValidSession(); 521 - if (session == null) { 522 - appLogger.w('No valid session for createComment'); 523 - return null; 402 + final token = session?.token; 403 + if (token == null) { 404 + throw Exception('No access token for createExif'); 524 405 } 525 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 526 - final issuer = session.session.issuer; 527 - final did = session.session.subject; 528 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 529 - final record = { 530 - 'collection': 'social.grain.comment', 531 - 'repo': did, 532 - 'record': { 533 - 'text': text, 534 - if (facets != null) 'facets': facets, 535 - 'subject': subject, 536 - if (focus != null) 'focus': focus, // focus is now a String 537 - if (replyTo != null) 'replyTo': replyTo, 538 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 539 - }, 540 - }; 541 - appLogger.i('Creating comment: $record'); 542 - final response = await dpopClient.send( 543 - method: 'POST', 544 - url: url, 545 - accessToken: session.session.accessToken, 546 - headers: {'Content-Type': 'application/json'}, 547 - body: jsonEncode(record), 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()), 548 411 ); 549 - if (response.statusCode != 200 && response.statusCode != 201) { 550 - appLogger.w('Failed to create comment: \\${response.statusCode} \\${response.body}'); 551 - return null; 412 + if (response.statusCode != 200) { 413 + throw Exception('Failed to create exif: ${response.statusCode} ${response.body}'); 552 414 } 553 - final result = jsonDecode(response.body) as Map<String, dynamic>; 554 - appLogger.i('Created comment result: $result'); 555 - return result['uri'] as String?; 415 + final json = jsonDecode(response.body); 416 + return CreateExifResponse.fromJson(json); 556 417 } 557 418 558 - Future<String?> createFavorite({required String galleryUri}) async { 419 + Future<CreateFollowResponse> createFollow({required CreateFollowRequest request}) async { 559 420 final session = await auth.getValidSession(); 560 - if (session == null) { 561 - appLogger.w('No valid session for createFavorite'); 562 - return null; 421 + final token = session?.token; 422 + if (token == null) { 423 + throw Exception('No access token for createFollow'); 563 424 } 564 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 565 - final issuer = session.session.issuer; 566 - final did = session.session.subject; 567 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 568 - final record = { 569 - 'collection': 'social.grain.favorite', 570 - 'repo': did, 571 - 'record': {'subject': galleryUri, 'createdAt': DateTime.now().toUtc().toIso8601String()}, 572 - }; 573 - appLogger.i('Creating favorite: $record'); 574 - final response = await dpopClient.send( 575 - method: 'POST', 576 - url: url, 577 - accessToken: session.session.accessToken, 578 - headers: {'Content-Type': 'application/json'}, 579 - body: jsonEncode(record), 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()), 580 430 ); 581 - if (response.statusCode != 200 && response.statusCode != 201) { 582 - appLogger.w('Failed to create favorite: \\${response.statusCode} \\${response.body}'); 583 - return null; 431 + if (response.statusCode != 200) { 432 + throw Exception('Failed to create follow: ${response.statusCode} ${response.body}'); 584 433 } 585 - final result = jsonDecode(response.body) as Map<String, dynamic>; 586 - appLogger.i('Created favorite result: $result'); 587 - return result['uri'] as String?; 434 + final json = jsonDecode(response.body); 435 + return CreateFollowResponse.fromJson(json); 588 436 } 589 437 590 - Future<String?> createFollow({required String followeeDid}) async { 438 + Future<DeleteFollowResponse> deleteFollow({required DeleteFollowRequest request}) async { 591 439 final session = await auth.getValidSession(); 592 - if (session == null) { 593 - appLogger.w('No valid session for createFollow'); 594 - return null; 440 + final token = session?.token; 441 + if (token == null) { 442 + throw Exception('No access token for deleteFollow'); 595 443 } 596 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 597 - final issuer = session.session.issuer; 598 - final did = session.session.subject; 599 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 600 - final record = { 601 - 'collection': 'social.grain.graph.follow', 602 - 'repo': did, 603 - 'record': {'subject': followeeDid, 'createdAt': DateTime.now().toUtc().toIso8601String()}, 604 - }; 605 - appLogger.i('Creating follow: $record'); 606 - final response = await dpopClient.send( 607 - method: 'POST', 608 - url: url, 609 - accessToken: session.session.accessToken, 610 - headers: {'Content-Type': 'application/json'}, 611 - body: jsonEncode(record), 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()), 612 449 ); 613 - if (response.statusCode != 200 && response.statusCode != 201) { 614 - appLogger.w('Failed to create follow: \\${response.statusCode} \\${response.body}'); 615 - return null; 450 + if (response.statusCode != 200) { 451 + throw Exception('Failed to delete follow: {response.statusCode} {response.body}'); 616 452 } 617 - final result = jsonDecode(response.body) as Map<String, dynamic>; 618 - appLogger.i('Created follow result: $result'); 619 - return result['uri'] as String?; 453 + final json = jsonDecode(response.body); 454 + return DeleteFollowResponse.fromJson(json); 620 455 } 621 456 622 - /// Deletes a record by its URI using DPoP authentication. 623 - /// Returns true on success, false on failure. 624 - Future<bool> deleteRecord(String uri) async { 457 + Future<DeletePhotoResponse> deletePhoto({required DeletePhotoRequest request}) async { 625 458 final session = await auth.getValidSession(); 626 - if (session == null) { 627 - appLogger.w('No valid session for deleteRecord'); 628 - return false; 459 + final token = session?.token; 460 + if (token == null) { 461 + throw Exception('No access token for deletePhoto'); 629 462 } 630 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 631 - final issuer = session.session.issuer; 632 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.deleteRecord'); 633 - final repo = session.session.subject; 634 - if (repo.isEmpty) { 635 - appLogger.w('No repo (DID) available from session for deleteRecord'); 636 - return false; 637 - } 638 - String? collection; 639 - String? rkey; 640 - try { 641 - final atUri = AtUri.parse(uri); 642 - collection = atUri.collection.toString(); 643 - rkey = atUri.rkey; 644 - } catch (e) { 645 - appLogger.w('Failed to parse collection from uri: $uri'); 646 - } 647 - if (collection == null || collection.isEmpty) { 648 - appLogger.w('No collection found in uri: $uri'); 649 - return false; 650 - } 651 - final payload = {'uri': uri, 'repo': repo, 'collection': collection, 'rkey': rkey}; 652 - appLogger.i('Deleting record: $payload'); 653 - final response = await dpopClient.send( 654 - method: 'POST', 655 - url: url, 656 - accessToken: session.session.accessToken, 657 - headers: {'Content-Type': 'application/json'}, 658 - body: jsonEncode(payload), 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()), 659 468 ); 660 - if (response.statusCode != 200 && response.statusCode != 204) { 661 - appLogger.w('Failed to delete record: \\${response.statusCode} \\${response.body}'); 662 - return false; 469 + if (response.statusCode != 200) { 470 + throw Exception('Failed to delete photo: ${response.statusCode} ${response.body}'); 663 471 } 664 - appLogger.i('Deleted record $uri'); 665 - return true; 472 + final json = jsonDecode(response.body); 473 + return DeletePhotoResponse.fromJson(json); 666 474 } 667 475 668 - /// Updates the current user's profile (displayName, description, avatar). 669 - /// If avatarFile is provided, uploads it as a blob and sets avatar. 670 - /// Returns true on success, false on failure. 671 - Future<bool> updateProfile({ 672 - required String displayName, 673 - required String description, 674 - File? avatarFile, 675 - }) async { 476 + Future<UploadPhotoResponse> uploadPhoto(File file) async { 676 477 final session = await auth.getValidSession(); 677 478 if (session == null) { 678 - appLogger.w('No valid session for updateProfile'); 679 - return false; 479 + appLogger.w('No valid session for uploadPhoto'); 480 + throw Exception('No valid session for uploadPhoto'); 680 481 } 681 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 682 - final issuer = session.session.issuer; 683 - final did = session.session.subject; 684 - // Fetch the raw profile record from atproto getRecord endpoint 685 - final getUrl = Uri.parse( 686 - '$issuer/xrpc/com.atproto.repo.getRecord?repo=$did&collection=social.grain.actor.profile&rkey=self', 687 - ); 688 - final getResp = await dpopClient.send( 689 - method: 'GET', 690 - url: getUrl, 691 - accessToken: session.session.accessToken, 692 - headers: {'Content-Type': 'application/json'}, 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, 693 496 ); 694 - if (getResp.statusCode != 200) { 497 + 498 + if (response.statusCode != 200 && response.statusCode != 201) { 695 499 appLogger.w( 696 - 'Failed to fetch raw profile record for update: \\${getResp.statusCode} \\${getResp.body}', 500 + 'Failed to upload photo: ${response.statusCode} ${response.body} (File: ${file.path}, MIME: $mimeType)', 697 501 ); 698 - return false; 502 + throw Exception('Failed to upload photo: ${response.statusCode} ${response.body}'); 699 503 } 700 - final recordJson = jsonDecode(getResp.body) as Map<String, dynamic>; 701 - var avatar = recordJson['value']?['avatar']; 702 - // If avatarFile is provided, upload it and set avatar 703 - if (avatarFile != null) { 704 - try { 705 - // Resize avatar before upload using photo_manip 706 - final resizeResult = await resizeImage(file: avatarFile); 707 - final blobResult = await uploadBlob(resizeResult.file); 708 - if (blobResult != null && blobResult['blob'] != null) { 709 - avatar = blobResult['blob']; 710 - } 711 - } catch (e) { 712 - appLogger.w('Failed to upload avatar: $e'); 713 - } 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'); 714 512 } 715 - // Update the profile record 716 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.putRecord'); 717 - final record = { 718 - 'collection': 'social.grain.actor.profile', 719 - 'repo': did, 720 - 'rkey': 'self', 721 - 'record': {'displayName': displayName, 'description': description, 'avatar': avatar}, 722 - }; 723 - appLogger.i('Updating profile: $record'); 724 - final response = await dpopClient.send( 725 - method: 'POST', 726 - url: url, 727 - accessToken: session.session.accessToken, 728 - headers: {'Content-Type': 'application/json'}, 729 - body: jsonEncode(record), 730 - ); 731 - if (response.statusCode != 200 && response.statusCode != 201) { 732 - appLogger.w('Failed to update profile: \\${response.statusCode} \\${response.body}'); 733 - return false; 734 - } 735 - appLogger.i('Profile updated successfully'); 736 - return true; 737 513 } 738 514 739 - /// Fetch followers for a given actor DID 740 - Future<FollowersResult> getFollowers({ 741 - required String actor, 742 - String? cursor, 743 - int limit = 50, 515 + Future<DeleteGalleryItemResponse> deleteGalleryItem({ 516 + required DeleteGalleryItemRequest request, 744 517 }) async { 745 - final uri = Uri.parse( 746 - '$_apiUrl/xrpc/social.grain.graph.getFollowers?actor=$actor&limit=$limit${cursor != null ? '&cursor=$cursor' : ''}', 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()), 747 528 ); 748 - final response = await http.get(uri, headers: {'Content-Type': 'application/json'}); 749 529 if (response.statusCode != 200) { 750 - throw Exception('Failed to fetch followers: \\${response.statusCode} \\${response.body}'); 530 + throw Exception('Failed to delete gallery item: ${response.statusCode} ${response.body}'); 751 531 } 752 532 final json = jsonDecode(response.body); 753 - return FollowersResult.fromJson(json); 533 + return DeleteGalleryItemResponse.fromJson(json); 754 534 } 755 535 756 - /// Fetch follows for a given actor DID 757 - Future<FollowsResult> getFollows({required String actor, String? cursor, int limit = 50}) async { 758 - final uri = Uri.parse( 759 - '$_apiUrl/xrpc/social.grain.graph.getFollows?actor=$actor&limit=$limit${cursor != null ? '&cursor=$cursor' : ''}', 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()), 760 549 ); 761 - final response = await http.get(uri, headers: {'Content-Type': 'application/json'}); 762 550 if (response.statusCode != 200) { 763 - throw Exception('Failed to fetch follows: \\${response.statusCode} \\${response.body}'); 551 + throw Exception('Failed to create gallery item: ${response.statusCode} ${response.body}'); 764 552 } 765 553 final json = jsonDecode(response.body); 766 - return FollowsResult.fromJson(json); 554 + return CreateGalleryItemResponse.fromJson(json); 767 555 } 768 556 769 - /// Updates the sort order of gallery items using com.atproto.repo.applyWrites 770 - /// [galleryUri]: The URI of the gallery (at://did/social.grain.gallery/rkey) 771 - /// [sortedItemUris]: List of item URIs in the desired order 772 - /// [itemsMeta]: List of GalleryItem meta objects (must include gallery, item, createdAt, uri) 773 - /// Returns true on success, false on failure 774 - Future<bool> updateGallerySortOrder({ 775 - required String galleryUri, 776 - required List<GalleryItem> orderedItems, 777 - }) async { 557 + Future<UpdateGalleryResponse> updateGallery({required UpdateGalleryRequest request}) async { 778 558 final session = await auth.getValidSession(); 779 - if (session == null) { 780 - appLogger.w('No valid session for updateGallerySortOrder'); 781 - return false; 559 + final token = session?.token; 560 + if (token == null) { 561 + throw Exception('No access token for updateGallery'); 782 562 } 783 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 784 - final issuer = session.session.issuer; 785 - final did = session.session.subject; 786 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.applyWrites'); 787 - 788 - final updates = <Map<String, dynamic>>[]; 789 - int position = 0; 790 - for (final item in orderedItems) { 791 - String rkey = ''; 792 - try { 793 - rkey = AtUri.parse(item.uri).rkey; 794 - } catch (_) {} 795 - updates.add({ 796 - '\$type': 'com.atproto.repo.applyWrites#update', 797 - 'collection': 'social.grain.gallery.item', 798 - 'rkey': rkey, 799 - 'value': { 800 - 'gallery': item.gallery, 801 - 'item': item.item, 802 - 'createdAt': item.createdAt, 803 - 'position': position, 804 - }, 805 - }); 806 - position++; 807 - } 808 - if (updates.isEmpty) { 809 - appLogger.w('No updates to apply for gallery sort order'); 810 - return false; 811 - } 812 - final payload = {'repo': did, 'validate': false, 'writes': updates}; 813 - appLogger.i('Applying gallery sort order updates: $payload'); 814 - final response = await dpopClient.send( 815 - method: 'POST', 816 - url: url, 817 - accessToken: session.session.accessToken, 818 - headers: {'Content-Type': 'application/json'}, 819 - body: jsonEncode(payload), 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()), 820 568 ); 821 - if (response.statusCode != 200 && response.statusCode != 201) { 822 - appLogger.w( 823 - 'Failed to apply gallery sort order: \\${response.statusCode} \\${response.body}', 824 - ); 825 - return false; 569 + if (response.statusCode != 200) { 570 + throw Exception('Failed to update gallery: ${response.statusCode} ${response.body}'); 826 571 } 827 - appLogger.i('Gallery sort order updated successfully'); 828 - return true; 572 + final json = jsonDecode(response.body); 573 + return UpdateGalleryResponse.fromJson(json); 829 574 } 830 575 831 - /// Updates a gallery's title and description. 832 - /// Returns true on success, false on failure. 833 - Future<bool> updateGallery({ 834 - required String galleryUri, 835 - required String title, 836 - required String description, 837 - required String createdAt, 838 - List<Map<String, dynamic>>? facets, 839 - }) async { 576 + Future<DeleteGalleryResponse> deleteGallery({required DeleteGalleryRequest request}) async { 840 577 final session = await auth.getValidSession(); 841 - if (session == null) { 842 - appLogger.w('No valid session for updateGallery'); 843 - return false; 578 + final token = session?.token; 579 + if (token == null) { 580 + throw Exception('No access token for deleteGallery'); 844 581 } 845 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 846 - final issuer = session.session.issuer; 847 - final did = session.session.subject; 848 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.putRecord'); 849 - // Extract rkey from galleryUri 850 - String rkey = ''; 851 - try { 852 - rkey = AtUri.parse(galleryUri).rkey; 853 - } catch (_) {} 854 - if (rkey.isEmpty) { 855 - appLogger.w('No rkey found in galleryUri: $galleryUri'); 856 - return false; 857 - } 858 - final record = { 859 - 'collection': 'social.grain.gallery', 860 - 'repo': did, 861 - 'rkey': rkey, 862 - 'record': { 863 - 'title': title, 864 - 'description': description, 865 - if (facets != null) 'facets': facets, 866 - 'updatedAt': DateTime.now().toUtc().toIso8601String(), 867 - 'createdAt': createdAt, 868 - }, 869 - }; 870 - appLogger.i('Updating gallery: $record'); 871 - final response = await dpopClient.send( 872 - method: 'POST', 873 - url: url, 874 - accessToken: session.session.accessToken, 875 - headers: {'Content-Type': 'application/json'}, 876 - body: jsonEncode(record), 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()), 877 587 ); 878 - if (response.statusCode != 200 && response.statusCode != 201) { 879 - appLogger.w('Failed to update gallery: ${response.statusCode} ${response.body}'); 880 - return false; 588 + if (response.statusCode != 200) { 589 + throw Exception('Failed to delete gallery: ${response.statusCode} ${response.body}'); 881 590 } 882 - appLogger.i('Gallery updated successfully'); 883 - return true; 591 + final json = jsonDecode(response.body); 592 + return DeleteGalleryResponse.fromJson(json); 884 593 } 885 594 886 - /// Creates a photo EXIF record in the social.grain.photo.exif collection. 887 - /// Returns the record URI on success, or null on failure. 888 - Future<String?> createPhotoExif({ 889 - required String photo, 890 - String? createdAt, 891 - String? dateTimeOriginal, 892 - int? exposureTime, 893 - int? fNumber, 894 - String? flash, 895 - int? focalLengthIn35mmFormat, 896 - int? iSO, 897 - String? lensMake, 898 - String? lensModel, 899 - String? make, 900 - String? model, 901 - }) async { 595 + Future<CreateGalleryResponse> createGallery({required CreateGalleryRequest request}) async { 902 596 final session = await auth.getValidSession(); 903 - if (session == null) { 904 - appLogger.w('No valid session for createPhotoExif'); 905 - return null; 597 + final token = session?.token; 598 + if (token == null) { 599 + throw Exception('No access token for createGallery'); 906 600 } 907 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 908 - final issuer = session.session.issuer; 909 - final did = session.session.subject; 910 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 911 - final record = { 912 - 'collection': 'social.grain.photo.exif', 913 - 'repo': did, 914 - 'record': { 915 - 'photo': photo, 916 - 'createdAt': createdAt ?? DateTime.now().toUtc().toIso8601String(), 917 - if (dateTimeOriginal != null) 'dateTimeOriginal': dateTimeOriginal, 918 - if (exposureTime != null) 'exposureTime': exposureTime, 919 - if (fNumber != null) 'fNumber': fNumber, 920 - if (flash != null) 'flash': flash, 921 - if (focalLengthIn35mmFormat != null) 'focalLengthIn35mmFormat': focalLengthIn35mmFormat, 922 - if (iSO != null) 'iSO': iSO, 923 - if (lensMake != null) 'lensMake': lensMake, 924 - if (lensModel != null) 'lensModel': lensModel, 925 - if (make != null) 'make': make, 926 - if (model != null) 'model': model, 927 - }, 928 - }; 929 - appLogger.i('Creating photo exif record: $record'); 930 - final response = await dpopClient.send( 931 - method: 'POST', 932 - url: url, 933 - accessToken: session.session.accessToken, 934 - headers: {'Content-Type': 'application/json'}, 935 - body: jsonEncode(record), 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()), 936 606 ); 937 - if (response.statusCode != 200 && response.statusCode != 201) { 938 - appLogger.w( 939 - 'Failed to create photo exif record: \\${response.statusCode} \\${response.body}', 940 - ); 941 - return null; 607 + if (response.statusCode != 200) { 608 + throw Exception('Failed to create gallery: ${response.statusCode} ${response.body}'); 942 609 } 943 - final result = jsonDecode(response.body) as Map<String, dynamic>; 944 - appLogger.i('Created photo exif record result: $result'); 945 - return result['uri'] as String?; 610 + final json = jsonDecode(response.body); 611 + return CreateGalleryResponse.fromJson(json); 946 612 } 947 613 948 - /// Updates multiple photo records in the social.grain.photo collection using applyWrites. 949 - /// Each photo in [updates] should have: photoUri, photo, aspectRatio, alt, createdAt 950 - /// Returns true on success, false on failure. 951 - Future<bool> updatePhotos(List<Map<String, dynamic>> updates) async { 614 + Future<DeleteFavoriteResponse> deleteFavorite({required DeleteFavoriteRequest request}) async { 952 615 final session = await auth.getValidSession(); 953 - if (session == null) { 954 - appLogger.w('No valid session for updatePhotosBatch'); 955 - return false; 616 + final token = session?.token; 617 + if (token == null) { 618 + throw Exception('No access token for deleteFavorite'); 956 619 } 957 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 958 - final issuer = session.session.issuer; 959 - final did = session.session.subject; 960 - final url = Uri.parse('$issuer/xrpc/com.atproto.repo.applyWrites'); 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 + } 961 632 962 - // Fetch current photo records for all photos 963 - final photoRecords = await fetchPhotoRecords(); 964 - 965 - final writes = <Map<String, dynamic>>[]; 966 - for (final update in updates) { 967 - String rkey = ''; 968 - try { 969 - rkey = AtUri.parse(update['photoUri'] as String).rkey; 970 - } catch (_) {} 971 - if (rkey.isEmpty) { 972 - appLogger.w('No rkey found in photoUri: ${update['photoUri']}'); 973 - continue; 974 - } 975 - 976 - // Get the full photo record for this photoUri 977 - final record = photoRecords[update['photoUri']]; 978 - if (record == null) { 979 - appLogger.w('No photo record found for photoUri: ${update['photoUri']}'); 980 - continue; 981 - } 982 - 983 - // Use provided values or fallback to the record's values 984 - final photoBlobRef = update['photo'] ?? record['photo']; 985 - final aspectRatio = update['aspectRatio'] ?? record['aspectRatio']; 986 - final createdAt = update['createdAt'] ?? record['createdAt']; 987 - 988 - if (photoBlobRef == null) { 989 - appLogger.w('No blobRef found for photoUri: ${update['photoUri']}'); 990 - continue; 991 - } 992 - 993 - writes.add({ 994 - '\$type': 'com.atproto.repo.applyWrites#update', 995 - 'collection': 'social.grain.photo', 996 - 'rkey': rkey, 997 - 'value': { 998 - 'photo': photoBlobRef, 999 - 'aspectRatio': aspectRatio, 1000 - 'alt': update['alt'] ?? '', 1001 - 'createdAt': createdAt, 1002 - }, 1003 - }); 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'); 1004 638 } 1005 - if (writes.isEmpty) { 1006 - appLogger.w('No valid photo updates to apply'); 1007 - return false; 1008 - } 1009 - final payload = {'repo': did, 'validate': false, 'writes': writes}; 1010 - appLogger.i('Applying batch photo updates: $payload'); 1011 - final response = await dpopClient.send( 1012 - method: 'POST', 1013 - url: url, 1014 - accessToken: session.session.accessToken, 1015 - headers: {'Content-Type': 'application/json'}, 1016 - body: jsonEncode(payload), 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()), 1017 644 ); 1018 - if (response.statusCode != 200 && response.statusCode != 201) { 1019 - appLogger.w('Failed to apply batch photo updates: ${response.statusCode} ${response.body}'); 1020 - return false; 645 + if (response.statusCode != 200) { 646 + throw Exception('Failed to create favorite: ${response.statusCode} ${response.body}'); 1021 647 } 1022 - appLogger.i('Batch photo updates applied successfully'); 1023 - return true; 648 + final json = jsonDecode(response.body); 649 + return CreateFavoriteResponse.fromJson(json); 1024 650 } 1025 651 1026 - /// Fetches the full photo record for each photo in social.grain.photo. 1027 - /// Returns a map of photoUri -> photo record (Map`<`String, dynamic`>`). 1028 - Future<Map<String, dynamic>> fetchPhotoRecords() async { 652 + Future<DeleteCommentResponse> deleteComment({required DeleteCommentRequest request}) async { 1029 653 final session = await auth.getValidSession(); 1030 - if (session == null) { 1031 - appLogger.w('No valid session for fetchPhotoRecords'); 1032 - return {}; 654 + final token = session?.token; 655 + if (token == null) { 656 + throw Exception('No access token for deleteComment'); 1033 657 } 1034 - final dpopClient = DpopHttpClient(dpopKey: session.session.dpopJwk); 1035 - final issuer = session.session.issuer; 1036 - final did = session.session.subject; 1037 - final url = Uri.parse( 1038 - '$issuer/xrpc/com.atproto.repo.listRecords?repo=$did&collection=social.grain.photo', 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()), 1039 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 + } 1040 670 1041 - final response = await dpopClient.send( 1042 - method: 'GET', 1043 - url: url, 1044 - accessToken: session.session.accessToken, 1045 - headers: {'Content-Type': 'application/json'}, 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()), 1046 682 ); 1047 - 1048 683 if (response.statusCode != 200) { 1049 - appLogger.w('Failed to list photo records: ${response.statusCode} ${response.body}'); 1050 - return {}; 684 + throw Exception('Failed to create comment: ${response.statusCode} ${response.body}'); 1051 685 } 1052 - 1053 - final json = jsonDecode(response.body) as Map<String, dynamic>; 1054 - final records = json['records'] as List<dynamic>? ?? []; 1055 - final photoRecords = <String, dynamic>{}; 1056 - 1057 - for (final record in records) { 1058 - final uri = record['uri'] as String?; 1059 - final value = record['value'] as Map<String, dynamic>?; 1060 - if (uri != null && value != null) { 1061 - photoRecords[uri] = value; 1062 - } 1063 - } 1064 - return photoRecords; 686 + final json = jsonDecode(response.body); 687 + return CreateCommentResponse.fromJson(json); 1065 688 } 1066 689 1067 - /// Notifies the server that the requesting account has seen notifications. 1068 - /// Sends a POST request with the current ISO timestamp as seenAt. 1069 690 Future<bool> updateSeen() async { 1070 691 final session = await auth.getValidSession(); 1071 692 final token = session?.token;
+76 -30
lib/auth.dart
··· 5 5 import 'package:grain/api.dart'; 6 6 import 'package:grain/app_logger.dart'; 7 7 import 'package:grain/main.dart'; 8 - import 'package:grain/models/atproto_session.dart'; 9 8 import 'package:grain/models/session.dart'; 10 9 11 10 class Auth { ··· 14 13 15 14 Future<bool> hasToken() async { 16 15 final session = await _loadSession(); 17 - return session != null && session.token.isNotEmpty && !isSessionExpired(session.session); 16 + return session != null && session.token.isNotEmpty && !isSessionExpired(session); 18 17 } 19 18 20 19 Future<void> login(String handle) async { 21 20 final apiUrl = AppConfig.apiUrl; 22 - final redirectedUrl = await FlutterWebAuth2.authenticate( 23 - url: '$apiUrl/oauth/login?client=native&handle=${Uri.encodeComponent(handle)}', 24 - callbackUrlScheme: 'grainflutter', 25 - ); 26 - final uri = Uri.parse(redirectedUrl); 27 - final token = uri.queryParameters['token']; 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 + } 28 43 29 - appLogger.i('Redirected URL: $redirectedUrl'); 30 44 appLogger.i('User signed in with handle: $handle'); 31 45 32 - final session = await apiService.fetchSession(token); 33 - if (session == null) { 34 - throw Exception('Failed to fetch session after login'); 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'); 35 64 } 65 + 66 + final session = Session( 67 + token: token, 68 + refreshToken: refreshToken, 69 + expiresAt: expiresAt, 70 + did: did, 71 + ); 36 72 await _saveSession(session); 37 73 } 38 74 39 75 Future<void> _saveSession(Session session) async { 40 - final atprotoSessionJson = jsonEncode(session.session.toJson()); 41 - await _storage.write(key: 'atproto_session', value: atprotoSessionJson); 42 - await _storage.write(key: 'api_token', value: session.token); 76 + final sessionJson = jsonEncode(session.toJson()); 77 + await _storage.write(key: 'session', value: sessionJson); 43 78 } 44 79 45 80 Future<Session?> _loadSession() async { 46 - final sessionJsonString = await _storage.read(key: 'atproto_session'); 47 - final token = await _storage.read(key: 'api_token'); 48 - if (sessionJsonString == null || token == null) return null; 81 + final sessionJsonString = await _storage.read(key: 'session'); 82 + if (sessionJsonString == null) return null; 49 83 50 84 try { 51 85 final sessionJson = jsonDecode(sessionJsonString); 52 - return Session(session: AtprotoSession.fromJson(sessionJson), token: token); 86 + return Session.fromJson(sessionJson); 53 87 } catch (e) { 54 88 // Optionally log or clear storage if corrupted 55 89 return null; 56 90 } 57 91 } 58 92 59 - bool isSessionExpired( 60 - AtprotoSession session, { 61 - Duration tolerance = const Duration(seconds: 30), 62 - }) { 93 + bool isSessionExpired(Session session, {Duration tolerance = const Duration(seconds: 30)}) { 63 94 final now = DateTime.now().toUtc(); 64 95 return session.expiresAt.subtract(tolerance).isBefore(now); 65 96 } ··· 70 101 // No session at all, do not attempt refresh 71 102 return null; 72 103 } 73 - if (isSessionExpired(session.session)) { 104 + if (isSessionExpired(session)) { 74 105 appLogger.w('Session is expired, attempting refresh'); 75 106 try { 76 - final refreshed = await apiService.fetchSession(); 77 - if (refreshed != null && !isSessionExpired(refreshed.session)) { 107 + final refreshed = await apiService.refreshSession(session); 108 + if (refreshed != null && !isSessionExpired(refreshed)) { 78 109 await _saveSession(refreshed); 79 110 appLogger.i('Session refreshed and saved'); 80 111 return refreshed; ··· 93 124 } 94 125 95 126 Future<void> clearSession() async { 96 - // Revoke session on the server 97 - await apiService.revokeSession(); 98 127 // Remove session from secure storage 99 - await _storage.delete(key: 'atproto_session'); 100 - await _storage.delete(key: 'api_token'); 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 + 101 136 // Clear any in-memory session/user data 102 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'); 103 149 } 104 150 } 105 151
-153
lib/dpop_client.dart
··· 1 - import 'dart:convert'; 2 - 3 - import 'package:crypto/crypto.dart'; 4 - import 'package:http/http.dart' as http; 5 - import 'package:jose/jose.dart'; 6 - import 'package:uuid/uuid.dart'; 7 - 8 - class DpopHttpClient { 9 - final JsonWebKey dpopKey; 10 - final Map<String, String> _nonces = {}; // origin -> nonce 11 - 12 - DpopHttpClient({required this.dpopKey}); 13 - 14 - /// Extract origin (scheme + host + port) from a URL 15 - String _extractOrigin(String url) { 16 - final uri = Uri.parse(url); 17 - final portPart = (uri.hasPort && uri.port != 80 && uri.port != 443) ? ':${uri.port}' : ''; 18 - return '${uri.scheme}://${uri.host}$portPart'; 19 - } 20 - 21 - /// Strip query and fragment from URL per spec 22 - String _buildHtu(String url) { 23 - final uri = Uri.parse(url); 24 - return '${uri.scheme}://${uri.host}${uri.path}'; 25 - } 26 - 27 - /// Calculate ath claim: base64url(sha256(access_token)) 28 - String _calculateAth(String accessToken) { 29 - final hash = sha256.convert(utf8.encode(accessToken)); 30 - return base64Url.encode(hash.bytes).replaceAll('=', ''); 31 - } 32 - 33 - /// Calculate the JWK Thumbprint for EC or RSA keys per RFC 7638. 34 - /// The input [jwk] is the public part of your key as a Map`<String, dynamic>`. 35 - /// 36 - /// For EC keys, required fields are: crv, kty, x, y 37 - /// For RSA keys, required fields are: e, kty, n 38 - String calculateJwkThumbprint(Map<String, dynamic> jwk) { 39 - late Map<String, String> ordered; 40 - 41 - if (jwk['kty'] == 'EC') { 42 - ordered = {'crv': jwk['crv'], 'kty': jwk['kty'], 'x': jwk['x'], 'y': jwk['y']}; 43 - } else if (jwk['kty'] == 'RSA') { 44 - ordered = {'e': jwk['e'], 'kty': jwk['kty'], 'n': jwk['n']}; 45 - } else { 46 - throw ArgumentError('Unsupported key type for thumbprint calculation'); 47 - } 48 - 49 - final jsonString = jsonEncode(ordered); 50 - 51 - final digest = sha256.convert(utf8.encode(jsonString)); 52 - return base64Url.encode(digest.bytes).replaceAll('=', ''); 53 - } 54 - 55 - /// Build the DPoP JWT proof 56 - Future<String> _buildProof({ 57 - required String htm, 58 - required String htu, 59 - String? nonce, 60 - String? ath, 61 - }) async { 62 - final now = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); 63 - final jti = Uuid().v4(); 64 - 65 - final publicJwk = Map<String, String>.from(dpopKey.toJson())..remove('d'); 66 - 67 - final payload = { 68 - 'htu': htu, 69 - 'htm': htm, 70 - 'iat': now, 71 - 'jti': jti, 72 - if (nonce != null) 'nonce': nonce, 73 - if (ath != null) 'ath': ath, 74 - }; 75 - 76 - final builder = JsonWebSignatureBuilder() 77 - ..jsonContent = payload 78 - ..addRecipient(dpopKey, algorithm: dpopKey.algorithm) 79 - ..setProtectedHeader('typ', 'dpop+jwt') 80 - ..setProtectedHeader('jwk', publicJwk); 81 - 82 - final jws = builder.build(); 83 - return jws.toCompactSerialization(); 84 - } 85 - 86 - /// Public method to send requests with DPoP proof, retries once on use_dpop_nonce error 87 - Future<http.Response> send({ 88 - required String method, 89 - required Uri url, 90 - required String accessToken, 91 - Map<String, String>? headers, 92 - Object? body, 93 - }) async { 94 - final origin = _extractOrigin(url.toString()); 95 - final nonce = _nonces[origin]; 96 - 97 - final htu = _buildHtu(url.toString()); 98 - final ath = _calculateAth(accessToken); 99 - 100 - final proof = await _buildProof(htm: method.toUpperCase(), htu: htu, nonce: nonce, ath: ath); 101 - 102 - // Compose headers, allowing override of Content-Type for raw uploads 103 - final requestHeaders = <String, String>{ 104 - 'Authorization': 'DPoP $accessToken', 105 - 'DPoP': proof, 106 - if (headers != null) ...headers, 107 - }; 108 - 109 - http.Response response; 110 - switch (method.toUpperCase()) { 111 - case 'GET': 112 - response = await http.get(url, headers: requestHeaders); 113 - break; 114 - case 'POST': 115 - response = await http.post(url, headers: requestHeaders, body: body); 116 - break; 117 - case 'PUT': 118 - response = await http.put(url, headers: requestHeaders, body: body); 119 - break; 120 - case 'DELETE': 121 - response = await http.delete(url, headers: requestHeaders, body: body); 122 - break; 123 - default: 124 - throw UnsupportedError('Unsupported HTTP method: $method'); 125 - } 126 - 127 - final newNonce = response.headers['dpop-nonce']; 128 - if (newNonce != null && newNonce != nonce) { 129 - // Save new nonce for origin 130 - _nonces[origin] = newNonce; 131 - } 132 - 133 - if (response.statusCode == 401) { 134 - final wwwAuth = response.headers['www-authenticate']; 135 - if (wwwAuth != null && 136 - wwwAuth.contains('DPoP') && 137 - wwwAuth.contains('error="use_dpop_nonce"') && 138 - newNonce != null && 139 - newNonce != nonce) { 140 - // Retry once with updated nonce 141 - return send( 142 - method: method, 143 - url: url, 144 - accessToken: accessToken, 145 - headers: headers, 146 - body: body, 147 - ); 148 - } 149 - } 150 - 151 - return response; 152 - } 153 - }
+1 -1
lib/main.dart
··· 129 129 } 130 130 131 131 void _handleSignOut(BuildContext context) async { 132 - await auth.clearSession(); 132 + await auth.logout(); 133 133 setState(() { 134 134 isSignedIn = false; 135 135 });
-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 - };
+1
lib/models/photo_exif.dart
··· 20 20 String? lensModel, 21 21 String? make, 22 22 String? model, 23 + Map<String, dynamic>? record, 23 24 }) = _PhotoExif; 24 25 25 26 factory PhotoExif.fromJson(Map<String, dynamic> json) => _$PhotoExifFromJson(json);
+31 -3
lib/models/photo_exif.freezed.dart
··· 36 36 String? get lensModel => throw _privateConstructorUsedError; 37 37 String? get make => throw _privateConstructorUsedError; 38 38 String? get model => throw _privateConstructorUsedError; 39 + Map<String, dynamic>? get record => throw _privateConstructorUsedError; 39 40 40 41 /// Serializes this PhotoExif to a JSON map. 41 42 Map<String, dynamic> toJson() => throw _privateConstructorUsedError; ··· 67 68 String? lensModel, 68 69 String? make, 69 70 String? model, 71 + Map<String, dynamic>? record, 70 72 }); 71 73 } 72 74 ··· 99 101 Object? lensModel = freezed, 100 102 Object? make = freezed, 101 103 Object? model = freezed, 104 + Object? record = freezed, 102 105 }) { 103 106 return _then( 104 107 _value.copyWith( ··· 158 161 ? _value.model 159 162 : model // ignore: cast_nullable_to_non_nullable 160 163 as String?, 164 + record: freezed == record 165 + ? _value.record 166 + : record // ignore: cast_nullable_to_non_nullable 167 + as Map<String, dynamic>?, 161 168 ) 162 169 as $Val, 163 170 ); ··· 188 195 String? lensModel, 189 196 String? make, 190 197 String? model, 198 + Map<String, dynamic>? record, 191 199 }); 192 200 } 193 201 ··· 219 227 Object? lensModel = freezed, 220 228 Object? make = freezed, 221 229 Object? model = freezed, 230 + Object? record = freezed, 222 231 }) { 223 232 return _then( 224 233 _$PhotoExifImpl( ··· 278 287 ? _value.model 279 288 : model // ignore: cast_nullable_to_non_nullable 280 289 as String?, 290 + record: freezed == record 291 + ? _value._record 292 + : record // ignore: cast_nullable_to_non_nullable 293 + as Map<String, dynamic>?, 281 294 ), 282 295 ); 283 296 } ··· 301 314 this.lensModel, 302 315 this.make, 303 316 this.model, 304 - }); 317 + final Map<String, dynamic>? record, 318 + }) : _record = record; 305 319 306 320 factory _$PhotoExifImpl.fromJson(Map<String, dynamic> json) => 307 321 _$$PhotoExifImplFromJson(json); ··· 339 353 final String? make; 340 354 @override 341 355 final String? model; 356 + final Map<String, dynamic>? _record; 357 + @override 358 + Map<String, dynamic>? get record { 359 + final value = _record; 360 + if (value == null) return null; 361 + if (_record is EqualUnmodifiableMapView) return _record; 362 + // ignore: implicit_dynamic_type 363 + return EqualUnmodifiableMapView(value); 364 + } 342 365 343 366 @override 344 367 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)'; 368 + 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, record: $record)'; 346 369 } 347 370 348 371 @override ··· 372 395 (identical(other.lensModel, lensModel) || 373 396 other.lensModel == lensModel) && 374 397 (identical(other.make, make) || other.make == make) && 375 - (identical(other.model, model) || other.model == model)); 398 + (identical(other.model, model) || other.model == model) && 399 + const DeepCollectionEquality().equals(other._record, _record)); 376 400 } 377 401 378 402 @JsonKey(includeFromJson: false, includeToJson: false) ··· 393 417 lensModel, 394 418 make, 395 419 model, 420 + const DeepCollectionEquality().hash(_record), 396 421 ); 397 422 398 423 /// Create a copy of PhotoExif ··· 425 450 final String? lensModel, 426 451 final String? make, 427 452 final String? model, 453 + final Map<String, dynamic>? record, 428 454 }) = _$PhotoExifImpl; 429 455 430 456 factory _PhotoExif.fromJson(Map<String, dynamic> json) = ··· 458 484 String? get make; 459 485 @override 460 486 String? get model; 487 + @override 488 + Map<String, dynamic>? get record; 461 489 462 490 /// Create a copy of PhotoExif 463 491 /// with the given fields replaced by the non-null parameter values.
+2
lib/models/photo_exif.g.dart
··· 22 22 lensModel: json['lensModel'] as String?, 23 23 make: json['make'] as String?, 24 24 model: json['model'] as String?, 25 + record: json['record'] as Map<String, dynamic>?, 25 26 ); 26 27 27 28 Map<String, dynamic> _$$PhotoExifImplToJson(_$PhotoExifImpl instance) => ··· 40 41 'lensModel': instance.lensModel, 41 42 'make': instance.make, 42 43 'model': instance.model, 44 + 'record': instance.record, 43 45 };
+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};
+6 -3
lib/models/session.dart
··· 1 1 import 'package:freezed_annotation/freezed_annotation.dart'; 2 2 3 - import 'atproto_session.dart'; 4 - 5 3 part 'session.freezed.dart'; 6 4 part 'session.g.dart'; 7 5 8 6 @freezed 9 7 class Session with _$Session { 10 - const factory Session({required AtprotoSession session, required String token}) = _Session; 8 + const factory Session({ 9 + required String token, 10 + required String refreshToken, 11 + required DateTime expiresAt, 12 + required String did, 13 + }) = _Session; 11 14 12 15 factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json); 13 16 }
+81 -38
lib/models/session.freezed.dart
··· 21 21 22 22 /// @nodoc 23 23 mixin _$Session { 24 - AtprotoSession get session => throw _privateConstructorUsedError; 25 24 String get token => throw _privateConstructorUsedError; 25 + String get refreshToken => throw _privateConstructorUsedError; 26 + DateTime get expiresAt => throw _privateConstructorUsedError; 27 + String get did => throw _privateConstructorUsedError; 26 28 27 29 /// Serializes this Session to a JSON map. 28 30 Map<String, dynamic> toJson() => throw _privateConstructorUsedError; ··· 38 40 factory $SessionCopyWith(Session value, $Res Function(Session) then) = 39 41 _$SessionCopyWithImpl<$Res, Session>; 40 42 @useResult 41 - $Res call({AtprotoSession session, String token}); 42 - 43 - $AtprotoSessionCopyWith<$Res> get session; 43 + $Res call({ 44 + String token, 45 + String refreshToken, 46 + DateTime expiresAt, 47 + String did, 48 + }); 44 49 } 45 50 46 51 /// @nodoc ··· 57 62 /// with the given fields replaced by the non-null parameter values. 58 63 @pragma('vm:prefer-inline') 59 64 @override 60 - $Res call({Object? session = null, Object? token = null}) { 65 + $Res call({ 66 + Object? token = null, 67 + Object? refreshToken = null, 68 + Object? expiresAt = null, 69 + Object? did = null, 70 + }) { 61 71 return _then( 62 72 _value.copyWith( 63 - session: null == session 64 - ? _value.session 65 - : session // ignore: cast_nullable_to_non_nullable 66 - as AtprotoSession, 67 73 token: null == token 68 74 ? _value.token 69 75 : token // ignore: cast_nullable_to_non_nullable 70 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, 71 89 ) 72 90 as $Val, 73 91 ); 74 92 } 75 - 76 - /// Create a copy of Session 77 - /// with the given fields replaced by the non-null parameter values. 78 - @override 79 - @pragma('vm:prefer-inline') 80 - $AtprotoSessionCopyWith<$Res> get session { 81 - return $AtprotoSessionCopyWith<$Res>(_value.session, (value) { 82 - return _then(_value.copyWith(session: value) as $Val); 83 - }); 84 - } 85 93 } 86 94 87 95 /// @nodoc ··· 92 100 ) = __$$SessionImplCopyWithImpl<$Res>; 93 101 @override 94 102 @useResult 95 - $Res call({AtprotoSession session, String token}); 96 - 97 - @override 98 - $AtprotoSessionCopyWith<$Res> get session; 103 + $Res call({ 104 + String token, 105 + String refreshToken, 106 + DateTime expiresAt, 107 + String did, 108 + }); 99 109 } 100 110 101 111 /// @nodoc ··· 111 121 /// with the given fields replaced by the non-null parameter values. 112 122 @pragma('vm:prefer-inline') 113 123 @override 114 - $Res call({Object? session = null, Object? token = null}) { 124 + $Res call({ 125 + Object? token = null, 126 + Object? refreshToken = null, 127 + Object? expiresAt = null, 128 + Object? did = null, 129 + }) { 115 130 return _then( 116 131 _$SessionImpl( 117 - session: null == session 118 - ? _value.session 119 - : session // ignore: cast_nullable_to_non_nullable 120 - as AtprotoSession, 121 132 token: null == token 122 133 ? _value.token 123 134 : token // ignore: cast_nullable_to_non_nullable 124 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, 125 148 ), 126 149 ); 127 150 } ··· 130 153 /// @nodoc 131 154 @JsonSerializable() 132 155 class _$SessionImpl implements _Session { 133 - const _$SessionImpl({required this.session, required this.token}); 156 + const _$SessionImpl({ 157 + required this.token, 158 + required this.refreshToken, 159 + required this.expiresAt, 160 + required this.did, 161 + }); 134 162 135 163 factory _$SessionImpl.fromJson(Map<String, dynamic> json) => 136 164 _$$SessionImplFromJson(json); 137 165 138 166 @override 139 - final AtprotoSession session; 167 + final String token; 168 + @override 169 + final String refreshToken; 170 + @override 171 + final DateTime expiresAt; 140 172 @override 141 - final String token; 173 + final String did; 142 174 143 175 @override 144 176 String toString() { 145 - return 'Session(session: $session, token: $token)'; 177 + return 'Session(token: $token, refreshToken: $refreshToken, expiresAt: $expiresAt, did: $did)'; 146 178 } 147 179 148 180 @override ··· 150 182 return identical(this, other) || 151 183 (other.runtimeType == runtimeType && 152 184 other is _$SessionImpl && 153 - (identical(other.session, session) || other.session == session) && 154 - (identical(other.token, token) || other.token == token)); 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)); 155 191 } 156 192 157 193 @JsonKey(includeFromJson: false, includeToJson: false) 158 194 @override 159 - int get hashCode => Object.hash(runtimeType, session, token); 195 + int get hashCode => 196 + Object.hash(runtimeType, token, refreshToken, expiresAt, did); 160 197 161 198 /// Create a copy of Session 162 199 /// with the given fields replaced by the non-null parameter values. ··· 174 211 175 212 abstract class _Session implements Session { 176 213 const factory _Session({ 177 - required final AtprotoSession session, 178 214 required final String token, 215 + required final String refreshToken, 216 + required final DateTime expiresAt, 217 + required final String did, 179 218 }) = _$SessionImpl; 180 219 181 220 factory _Session.fromJson(Map<String, dynamic> json) = _$SessionImpl.fromJson; 182 221 183 - @override 184 - AtprotoSession get session; 185 222 @override 186 223 String get token; 224 + @override 225 + String get refreshToken; 226 + @override 227 + DateTime get expiresAt; 228 + @override 229 + String get did; 187 230 188 231 /// Create a copy of Session 189 232 /// with the given fields replaced by the non-null parameter values.
+9 -2
lib/models/session.g.dart
··· 8 8 9 9 _$SessionImpl _$$SessionImplFromJson(Map<String, dynamic> json) => 10 10 _$SessionImpl( 11 - session: AtprotoSession.fromJson(json['session'] as Map<String, dynamic>), 12 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, 13 15 ); 14 16 15 17 Map<String, dynamic> _$$SessionImplToJson(_$SessionImpl instance) => 16 - <String, dynamic>{'session': instance.session, 'token': instance.token}; 18 + <String, dynamic>{ 19 + 'token': instance.token, 20 + 'refreshToken': instance.refreshToken, 21 + 'expiresAt': instance.expiresAt.toIso8601String(), 22 + 'did': instance.did, 23 + };
+22
lib/providers/actor_search_provider.dart
··· 1 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 + 3 + import '../api.dart'; 4 + import '../models/profile.dart'; 5 + 6 + final actorSearchProvider = StateNotifierProvider<ActorSearchNotifier, Map<String, List<Profile>>>( 7 + (ref) => ActorSearchNotifier(), 8 + ); 9 + 10 + class ActorSearchNotifier extends StateNotifier<Map<String, List<Profile>>> { 11 + ActorSearchNotifier() : super({}); 12 + 13 + Future<List<Profile>> search(String query) async { 14 + if (query.isEmpty) return []; 15 + if (state.containsKey(query)) { 16 + return state[query]!; 17 + } 18 + final results = await apiService.searchActors(query); 19 + state = {...state, query: results}; 20 + return results; 21 + } 22 + }
+116 -120
lib/providers/gallery_cache_provider.dart
··· 1 1 import 'dart:async'; 2 2 import 'dart:io'; 3 - import 'dart:ui' as ui; 4 3 5 - import 'package:bluesky_text/bluesky_text.dart'; 6 4 import 'package:flutter/foundation.dart'; 7 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'; 8 9 import 'package:image_picker/image_picker.dart'; 9 10 import 'package:riverpod_annotation/riverpod_annotation.dart'; 10 11 11 12 import '../api.dart'; 13 + import '../app_logger.dart'; 12 14 import '../models/gallery.dart'; 13 15 import '../models/gallery_item.dart'; 14 16 import '../photo_manip.dart'; ··· 40 42 41 43 void setGalleriesForActor(String did, List<Gallery> galleries) { 42 44 setGalleries(galleries); 43 - // Optionally, you could keep a mapping of actor DID to gallery URIs if needed 44 - } 45 - 46 - Future<List<Map<String, dynamic>>> _extractFacets(String text) async { 47 - final blueskyText = BlueskyText(text); 48 - final entities = blueskyText.entities; 49 - final facets = await entities.toFacets(); 50 - return List<Map<String, dynamic>>.from(facets); 51 45 } 52 46 53 47 Future<void> toggleFavorite(String uri) async { ··· 59 53 bool success = false; 60 54 String? newFavUri; 61 55 if (isFav) { 62 - newFavUri = await apiService.createFavorite(galleryUri: uri); 63 - success = newFavUri != null; 56 + final response = await apiService.createFavorite( 57 + request: CreateFavoriteRequest(subject: latestGallery.uri), 58 + ); 59 + newFavUri = response.favoriteUri; 60 + success = true; 64 61 } else { 65 - success = await apiService.deleteRecord(latestGallery.viewer?.fav ?? uri); 62 + final deleteResponse = await apiService.deleteFavorite( 63 + request: DeleteFavoriteRequest(uri: latestGallery.viewer?.fav ?? uri), 64 + ); 65 + success = deleteResponse.success; 66 66 newFavUri = null; 67 67 } 68 68 if (success) { 69 69 final newCount = (latestGallery.favCount ?? 0) + (isFav ? 1 : -1); 70 70 final updatedViewer = latestGallery.viewer?.copyWith(fav: newFavUri); 71 - state = {...state, uri: latestGallery.copyWith(favCount: newCount, viewer: updatedViewer)}; 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 + } 72 83 } 73 84 } 74 85 ··· 77 88 final gallery = state[galleryUri]; 78 89 if (gallery == null) return; 79 90 // Call backend to delete the gallery item 80 - await apiService.deleteRecord(galleryItemUri); 91 + await apiService.deleteGalleryItem(request: DeleteGalleryItemRequest(uri: galleryItemUri)); 81 92 // Remove by gallery item record URI, not photo URI 82 93 final updatedItems = gallery.items.where((p) => p.gallery?.item != galleryItemUri).toList(); 83 94 final updatedGallery = gallery.copyWith(items: updatedItems); 84 95 state = {...state, galleryUri: updatedGallery}; 85 96 } 86 97 87 - /// Uploads multiple photos, gets their dimensions, resizes them, creates the photos, and adds them as gallery items. 88 - /// At the end, polls for the updated gallery items and updates the cache. 89 - /// Returns the list of new photoUris if successful, or empty list otherwise. 90 98 Future<List<String>> uploadAndAddPhotosToGallery({ 91 99 required String galleryUri, 92 100 required List<XFile> xfiles, 93 101 int? startPosition, 94 102 bool includeExif = true, 103 + void Function(int imageIndex, double progress)? onProgress, 95 104 }) async { 96 105 // Fetch the latest gallery from the API to avoid stale state 97 106 final latestGallery = await apiService.getGallery(uri: galleryUri); ··· 103 112 final int positionOffset = startPosition ?? initialCount; 104 113 final List<String> photoUris = []; 105 114 int position = positionOffset; 106 - for (final xfile in xfiles) { 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 + 107 120 final file = File(xfile.path); 108 121 // Parse EXIF if requested 109 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 + 110 130 // Resize the image 111 131 final resizedResult = await compute<File, ResizeResult>((f) => resizeImage(file: f), file); 112 132 // Upload the blob 113 - final blobResult = await apiService.uploadBlob(resizedResult.file); 114 - if (blobResult == null) continue; 115 - // Get image dimensions 116 - final bytes = await xfile.readAsBytes(); 117 - final completer = Completer<Map<String, int>>(); 118 - ui.decodeImageFromList(bytes, (image) { 119 - completer.complete({'width': image.width, 'height': image.height}); 120 - }); 121 - final dims = await completer.future; 122 - // Create the photo 123 - final photoUri = await apiService.createPhoto( 124 - blob: blobResult, 125 - width: dims['width']!, 126 - height: dims['height']!, 127 - ); 128 - if (photoUri == null) continue; 129 - 133 + final res = await apiService.uploadPhoto(resizedResult.file); 134 + if (res.photoUri.isEmpty) continue; 130 135 // If EXIF data was found, create photo exif record 131 136 if (exif != null) { 132 - await apiService.createPhotoExif( 133 - photo: photoUri, 134 - dateTimeOriginal: exif['dateTimeOriginal'] as String?, 135 - exposureTime: exif['exposureTime'] as int?, 136 - fNumber: exif['fNumber'] as int?, 137 - flash: exif['flash'] as String?, 138 - focalLengthIn35mmFormat: exif['focalLengthIn35mmFilm'] as int?, 139 - iSO: exif['iSOSpeedRatings'] as int?, 140 - lensMake: exif['lensMake'] as String?, 141 - lensModel: exif['lensModel'] as String?, 142 - make: exif['make'] as String?, 143 - model: exif['model'] as String?, 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 + ), 144 151 ); 145 152 } 146 153 147 154 // Create the gallery item 148 155 await apiService.createGalleryItem( 149 - galleryUri: galleryUri, 150 - photoUri: photoUri, 151 - position: position, 156 + request: CreateGalleryItemRequest( 157 + galleryUri: galleryUri, 158 + photoUri: res.photoUri, 159 + position: position, 160 + ), 152 161 ); 153 - photoUris.add(photoUri); 162 + photoUris.add(res.photoUri); 154 163 position++; 155 164 } 156 - // Poll for updated gallery items 157 - final expectedCount = (gallery?.items.length ?? 0) + photoUris.length; 158 - await apiService.pollGalleryItems(galleryUri: galleryUri, expectedCount: expectedCount); 159 165 // Fetch the updated gallery and update the cache 160 166 final updatedGallery = await apiService.getGallery(uri: galleryUri); 161 167 if (updatedGallery != null) { ··· 171 177 required String description, 172 178 required List<XFile> xfiles, 173 179 bool includeExif = true, 180 + void Function(int imageIndex, double progress)? onProgress, 174 181 }) async { 175 - // Extract facets from description 176 - final facetsList = await _extractFacets(description); 177 - final facets = facetsList.isEmpty ? null : facetsList; 178 - // Create the gallery with facets 179 - final galleryUri = await apiService.createGallery( 180 - title: title, 181 - description: description, 182 - facets: facets, 182 + final res = await apiService.createGallery( 183 + request: CreateGalleryRequest(title: title, description: description), 183 184 ); 184 - if (galleryUri == null) return (null, <String>[]); 185 185 // Upload and add photos 186 186 final photoUris = await uploadAndAddPhotosToGallery( 187 - galleryUri: galleryUri, 187 + galleryUri: res.galleryUri, 188 188 xfiles: xfiles, 189 189 includeExif: includeExif, 190 + onProgress: onProgress, 190 191 ); 191 - return (galleryUri, photoUris); 192 + return (res.galleryUri, photoUris); 192 193 } 193 194 194 - /// Creates gallery items for existing photoUris, polls for updated gallery items, and updates the cache. 195 + /// Creates gallery items for existing photoUris and updates the cache. 195 196 /// Returns the list of new gallery item URIs if successful, or empty list otherwise. 196 197 Future<List<String>> addGalleryItemsToGallery({ 197 198 required String galleryUri, ··· 210 211 int position = positionOffset; 211 212 for (final photoUri in photoUris) { 212 213 // Create the gallery item 213 - final itemUri = await apiService.createGalleryItem( 214 - galleryUri: galleryUri, 215 - photoUri: photoUri, 216 - position: position, 214 + final res = await apiService.createGalleryItem( 215 + request: CreateGalleryItemRequest( 216 + galleryUri: galleryUri, 217 + photoUri: photoUri, 218 + position: position, 219 + ), 217 220 ); 218 - if (itemUri != null) { 219 - galleryItemUris.add(itemUri); 221 + if (res.itemUri.isNotEmpty) { 222 + galleryItemUris.add(res.itemUri); 220 223 position++; 221 224 } 222 225 } 223 - // Poll for updated gallery items 224 - final expectedCount = (gallery?.items.length ?? 0) + galleryItemUris.length; 225 - await apiService.pollGalleryItems(galleryUri: galleryUri, expectedCount: expectedCount); 226 226 // Fetch the updated gallery and update the cache 227 227 final updatedGallery = await apiService.getGallery(uri: galleryUri); 228 228 if (updatedGallery != null) { ··· 233 233 234 234 /// Deletes a gallery from the backend and removes it from the cache. 235 235 Future<void> deleteGallery(String uri) async { 236 - // Fetch the latest gallery from backend to ensure all items are deleted 237 - final gallery = await apiService.getGallery(uri: uri); 238 - if (gallery != null) { 239 - // Delete all gallery item records 240 - for (final item in gallery.items) { 241 - final itemUri = item.gallery?.item; 242 - if (itemUri != null && itemUri.isNotEmpty) { 243 - await apiService.deleteRecord(itemUri); 244 - } 245 - } 246 - } 247 - await apiService.deleteRecord(uri); 236 + await apiService.deleteGallery(request: DeleteGalleryRequest(uri: uri)); 248 237 removeGallery(uri); 249 238 } 250 239 ··· 269 258 for (int i = 0; i < orderedItems.length; i++) { 270 259 orderedItems[i] = orderedItems[i].copyWith(position: i); 271 260 } 272 - await apiService.updateGallerySortOrder(galleryUri: galleryUri, orderedItems: orderedItems); 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 + } 273 272 // Update cache with new order 274 273 final updatedPhotos = orderedItems 275 274 .where((item) => gallery.items.any((p) => p.uri == item.item)) ··· 282 281 state = {...state, galleryUri: updatedGallery}; 283 282 } 284 283 285 - /// Updates gallery details (title, description), polls for cid change, and updates cache. 284 + /// Updates gallery details (title, description) and updates cache. 286 285 Future<bool> updateGalleryDetails({ 287 286 required String galleryUri, 288 287 required String title, 289 288 required String description, 290 289 required String createdAt, 291 290 }) async { 292 - final prevGallery = state[galleryUri]; 293 - final prevCid = prevGallery?.cid; 294 - // Extract facets from description 295 - final facetsList = await _extractFacets(description); 296 - final facets = facetsList.isEmpty ? null : facetsList; 297 - final success = await apiService.updateGallery( 298 - galleryUri: galleryUri, 299 - title: title, 300 - description: description, 301 - createdAt: createdAt, 302 - facets: facets, 303 - ); 304 - if (success) { 305 - final start = DateTime.now(); 306 - const timeout = Duration(seconds: 20); 307 - const pollInterval = Duration(milliseconds: 1000); 308 - Gallery? updatedGallery; 309 - while (DateTime.now().difference(start) < timeout) { 310 - updatedGallery = await apiService.getGallery(uri: galleryUri); 311 - if (updatedGallery != null && updatedGallery.cid != prevCid) { 312 - break; 313 - } 314 - await Future.delayed(pollInterval); 315 - } 316 - if (updatedGallery != null) { 317 - state = {...state, galleryUri: updatedGallery}; 318 - } 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}; 319 306 } 320 - return success; 307 + return true; 321 308 } 322 309 323 310 /// Fetches timeline galleries from the API and updates the cache. ··· 335 322 required String galleryUri, 336 323 required List<Map<String, dynamic>> altUpdates, 337 324 }) async { 338 - final success = await apiService.updatePhotos(altUpdates); 339 - if (!success) return false; 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; 340 336 341 337 // Update the gallery photos' alt text in the cache manually 342 338 final gallery = state[galleryUri];
+1 -1
lib/providers/gallery_cache_provider.g.dart
··· 6 6 // RiverpodGenerator 7 7 // ************************************************************************** 8 8 9 - String _$galleryCacheHash() => r'cd0665a1f246bd700195cf5ce50893ba73b878f9'; 9 + String _$galleryCacheHash() => r'd604bfc71f008251a36d7943b99294728c31de1f'; 10 10 11 11 /// Holds a cache of galleries by URI. 12 12 ///
+10 -24
lib/providers/gallery_thread_cache_provider.dart
··· 1 - import 'package:bluesky_text/bluesky_text.dart'; 2 1 import 'package:grain/api.dart'; 3 2 import 'package:grain/models/comment.dart'; 4 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'; 5 6 import 'package:grain/providers/gallery_cache_provider.dart'; 6 7 import 'package:riverpod_annotation/riverpod_annotation.dart'; 7 8 ··· 65 66 } 66 67 } 67 68 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 69 Future<bool> createComment({required String text, String? replyTo, String? focus}) async { 76 70 try { 77 - final facetsList = await _extractFacets(text); 78 - final facets = facetsList.isEmpty ? null : facetsList; 79 - final uri = await apiService.createComment( 71 + final request = CreateCommentRequest( 80 72 text: text, 81 73 subject: galleryUri, 82 74 replyTo: replyTo, 83 - facets: facets, 84 75 focus: focus, 85 76 ); 86 - if (uri != null) { 87 - final thread = await apiService.pollGalleryThreadComments( 88 - galleryUri: galleryUri, 89 - expectedCount: state.comments.length + 1, 90 - ); 77 + final res = await apiService.createComment(request: request); 78 + if (res.commentUri.isNotEmpty) { 79 + final thread = await apiService.getGalleryThread(uri: galleryUri); 91 80 if (thread != null) { 92 81 state = state.copyWith(gallery: thread.gallery, comments: thread.comments); 93 82 // Update the gallery cache with the latest gallery ··· 102 91 } 103 92 104 93 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 - ); 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); 112 98 if (thread != null) { 113 99 state = state.copyWith(gallery: thread.gallery, comments: thread.comments); 114 100 // Update the gallery cache with the latest gallery
+1 -1
lib/providers/gallery_thread_cache_provider.g.dart
··· 6 6 // RiverpodGenerator 7 7 // ************************************************************************** 8 8 9 - String _$galleryThreadHash() => r'fa270baa7dd229fdd6b4e2610cf232aed53ed3db'; 9 + String _$galleryThreadHash() => r'80fa9b4e19b9d7606654fff8549516d4654dfa87'; 10 10 11 11 /// Copied from Dart SDK 12 12 class _SystemHash {
+60 -70
lib/providers/profile_provider.dart
··· 1 1 import 'dart:io'; 2 2 3 3 import 'package:bluesky_text/bluesky_text.dart'; 4 + import 'package:flutter/foundation.dart'; 4 5 import 'package:grain/api.dart'; 5 6 import 'package:grain/models/gallery.dart'; 6 - import 'package:grain/models/profile.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'; 7 10 import 'package:grain/models/profile_with_galleries.dart'; 11 + import 'package:grain/photo_manip.dart'; 8 12 import 'package:grain/providers/gallery_cache_provider.dart'; 9 13 import 'package:image_picker/image_picker.dart'; 10 14 import 'package:riverpod_annotation/riverpod_annotation.dart'; ··· 18 22 return _fetchProfile(did); 19 23 } 20 24 21 - // @TODO: Facets don't always render correctly. 22 - List<Map<String, dynamic>>? _filterValidFacets( 23 - List<Map<String, dynamic>>? computedFacets, 24 - String desc, 25 - ) { 26 - if (computedFacets == null) return null; 27 - return computedFacets.where((facet) { 28 - final index = facet['index']; 29 - if (index is Map) { 30 - final start = index['byteStart'] ?? 0; 31 - final end = index['byteEnd'] ?? 0; 32 - return start is int && end is int && start >= 0 && end > start && end <= desc.length; 33 - } 34 - final start = facet['index'] ?? facet['offset'] ?? 0; 35 - final end = facet['end']; 36 - final length = facet['length']; 37 - if (end is int && start is int) { 38 - return start >= 0 && end > start && end <= desc.length; 39 - } else if (length is int && start is int) { 40 - return start >= 0 && length > 0 && start + length <= desc.length; 41 - } 42 - return false; 43 - }).toList(); 44 - } 45 - 46 - // Extract facet computation and filtering for reuse 47 - Future<List<Map<String, dynamic>>?> computeAndFilterFacets(String? description) async { 25 + // Extract facets 26 + Future<List<Map<String, dynamic>>?> _extractFacets(String? description) async { 48 27 final desc = description ?? ''; 49 28 if (desc.isEmpty) return null; 50 29 try { 51 30 final blueskyText = BlueskyText(desc); 52 31 final entities = blueskyText.entities; 53 - final computedFacets = await entities.toFacets(); 54 - return _filterValidFacets(computedFacets, desc); 32 + return entities.toFacets(); 55 33 } catch (_) { 56 34 return null; 57 35 } ··· 62 40 final galleries = await apiService.fetchActorGalleries(did: did); 63 41 final favs = await apiService.getActorFavs(did: did); 64 42 if (profile != null) { 65 - final facets = await computeAndFilterFacets(profile.description); 43 + final facets = await _extractFacets(profile.description); 66 44 return ProfileWithGalleries( 67 45 profile: profile.copyWith(descriptionFacets: facets), 68 46 galleries: galleries, ··· 82 60 required String description, 83 61 dynamic avatarFile, 84 62 }) 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 63 File? file; 96 64 if (avatarFile is XFile) { 97 65 file = File(avatarFile.path); ··· 100 68 } else { 101 69 file = null; 102 70 } 103 - final success = await apiService.updateProfile( 104 - displayName: displayName, 105 - description: description, 106 - avatarFile: file, 71 + final profileRes = await apiService.updateProfile( 72 + request: UpdateProfileRequest(displayName: displayName, description: description), 107 73 ); 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 74 + bool avatarSuccess = true; 75 + if (file != null) { 76 + final resizedResult = await compute<File, ResizeResult>((f) => resizeImage(file: f), file); 77 + final avatarRes = await apiService.updateAvatar(avatarFile: resizedResult.file); 78 + avatarSuccess = avatarRes.success; 79 + } 80 + // Refetch updated profile if update succeeded 81 + if (profileRes.success || avatarSuccess) { 82 + final updated = await apiService.fetchProfile(did: did); 123 83 if (updated != null) { 124 84 final galleries = await apiService.fetchActorGalleries(did: did); 125 - final facets = await computeAndFilterFacets(updated.description); 126 - // Update the gallery cache provider 85 + final facets = await _extractFacets(updated.description); 127 86 ref.read(galleryCacheProvider.notifier).setGalleriesForActor(did, galleries); 128 87 state = AsyncValue.data( 129 88 ProfileWithGalleries( ··· 134 93 ); 135 94 } else { 136 95 state = const AsyncValue.data(null); 137 - } 138 - if (updated == null) { 139 96 await refresh(); 140 97 } 98 + return true; 99 + } else { 100 + await refresh(); 101 + return false; 141 102 } 142 - return success; 143 103 } 144 104 145 105 Future<void> toggleFollow(String? followerDid) async { ··· 150 110 final followUri = viewer?.following; 151 111 if (followUri != null && followUri.isNotEmpty) { 152 112 // Unfollow 153 - final success = await apiService.deleteRecord(followUri); 154 - if (success) { 113 + final res = await apiService.deleteFollow(request: DeleteFollowRequest(uri: followUri)); 114 + if (res.success) { 155 115 final updatedProfile = profile.copyWith( 156 116 viewer: viewer?.copyWith(following: null), 157 117 followersCount: (profile.followersCount ?? 1) - 1, ··· 166 126 } 167 127 } else { 168 128 // Follow 169 - final newFollowUri = await apiService.createFollow(followeeDid: did); 170 - if (newFollowUri != null) { 129 + final res = await apiService.createFollow(request: CreateFollowRequest(subject: followerDid)); 130 + if (res.followUri.isNotEmpty) { 171 131 final updatedProfile = profile.copyWith( 172 - viewer: viewer?.copyWith(following: newFollowUri), 132 + viewer: viewer?.copyWith(following: res.followUri), 173 133 followersCount: (profile.followersCount ?? 0) + 1, 174 134 ); 175 135 state = AsyncValue.data( ··· 212 172 profile: updatedProfile, 213 173 galleries: updatedGalleries, 214 174 favs: currentProfile.favs, 175 + ), 176 + ); 177 + } 178 + } 179 + 180 + /// Adds a gallery to the user's favorites in the profile state. 181 + void addFavorite(Gallery gallery) { 182 + final currentProfile = state.value; 183 + if (currentProfile != null) { 184 + final updatedFavs = [gallery, ...?currentProfile.favs]; 185 + state = AsyncValue.data( 186 + ProfileWithGalleries( 187 + profile: currentProfile.profile, 188 + galleries: currentProfile.galleries, 189 + favs: updatedFavs, 190 + ), 191 + ); 192 + } 193 + } 194 + 195 + /// Removes a gallery from the user's favorites in the profile state by URI. 196 + void removeFavorite(String galleryUri) { 197 + final currentProfile = state.value; 198 + if (currentProfile != null) { 199 + final updatedFavs = (currentProfile.favs ?? []).where((g) => g.uri != galleryUri).toList(); 200 + state = AsyncValue.data( 201 + ProfileWithGalleries( 202 + profile: currentProfile.profile, 203 + galleries: currentProfile.galleries, 204 + favs: updatedFavs, 215 205 ), 216 206 ); 217 207 }
+1 -1
lib/providers/profile_provider.g.dart
··· 6 6 // RiverpodGenerator 7 7 // ************************************************************************** 8 8 9 - String _$profileNotifierHash() => r'2ec9237c3a60bff58175d2d39f37b050eecdba78'; 9 + String _$profileNotifierHash() => r'4b8e3a8d4363beb885ead4ae7ce9c52101a6bf96'; 10 10 11 11 /// Copied from Dart SDK 12 12 class _SystemHash {
+4 -10
lib/screens/explore_page.dart
··· 6 6 import 'package:grain/app_icons.dart'; 7 7 import 'package:grain/models/profile.dart'; 8 8 import 'package:grain/providers/profile_provider.dart'; 9 + import 'package:grain/screens/profile_page.dart'; 9 10 import 'package:grain/widgets/app_image.dart'; 10 11 import 'package:grain/widgets/plain_text_field.dart'; 11 12 import 'package:shared_preferences/shared_preferences.dart'; 12 - 13 - import 'profile_page.dart'; 14 13 15 14 class ExplorePage extends ConsumerStatefulWidget { 16 15 const ExplorePage({super.key}); ··· 41 40 final profiles = <Profile>[]; 42 41 for (final did in dids) { 43 42 try { 44 - final asyncProfile = ref.watch(profileNotifierProvider(did)); 45 - if (asyncProfile.hasValue && asyncProfile.value != null) { 46 - profiles.add(asyncProfile.value!.profile); 47 - } else { 48 - final profileWithGalleries = await ref.refresh(profileNotifierProvider(did).future); 49 - if (profileWithGalleries != null) { 50 - profiles.add(profileWithGalleries.profile); 51 - } 43 + final profileWithGalleries = await ref.read(profileNotifierProvider(did).future); 44 + if (profileWithGalleries != null) { 45 + profiles.add(profileWithGalleries.profile); 52 46 } 53 47 } catch (_) {} 54 48 }
+11 -4
lib/screens/gallery_edit_photos_sheet.dart
··· 222 222 context, 223 223 actorDid: actorDid, 224 224 galleryUri: widget.galleryUri, 225 - onSelect: (photos) { 226 - setState(() { 227 - _photos.addAll(photos); 228 - }); 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 + } 229 236 }, 230 237 ); 231 238 },
+8 -10
lib/screens/gallery_page.dart
··· 273 273 ); 274 274 } 275 275 : null, 276 - child: Row( 277 - crossAxisAlignment: CrossAxisAlignment.center, 276 + child: Column( 277 + crossAxisAlignment: CrossAxisAlignment.start, 278 278 children: [ 279 279 Text( 280 280 gallery.creator?.displayName ?? '', ··· 282 282 fontWeight: FontWeight.w600, 283 283 ), 284 284 ), 285 - if ((gallery.creator?.displayName ?? '').isNotEmpty && 286 - (gallery.creator?.handle ?? '').isNotEmpty) 287 - const SizedBox(width: 8), 288 - Text( 289 - '@${gallery.creator?.handle ?? ''}', 290 - style: theme.textTheme.bodyMedium?.copyWith( 291 - color: theme.hintColor, 285 + if ((gallery.creator?.handle ?? '').isNotEmpty) 286 + Text( 287 + '@${gallery.creator?.handle ?? ''}', 288 + style: theme.textTheme.bodyMedium?.copyWith( 289 + color: theme.hintColor, 290 + ), 292 291 ), 293 - ), 294 292 ], 295 293 ), 296 294 ),
+6 -1
lib/screens/library_photos_select_sheet.dart
··· 40 40 Future<void> _fetchPhotos() async { 41 41 setState(() => _loading = true); 42 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(); 43 48 if (mounted) { 44 49 setState(() { 45 - _photos = photos; 50 + _photos = filteredPhotos; 46 51 _loading = false; 47 52 }); 48 53 }
+601
lib/screens/photo_library_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/gallery_photo.dart'; 5 + import 'package:grain/widgets/app_image.dart'; 6 + import 'package:grain/widgets/gallery_photo_view.dart'; 7 + 8 + class PhotoGroup { 9 + final String title; 10 + final List<GalleryPhoto> photos; 11 + final DateTime? sortDate; 12 + 13 + PhotoGroup({required this.title, required this.photos, this.sortDate}); 14 + } 15 + 16 + class PhotoLibraryPage extends StatefulWidget { 17 + const PhotoLibraryPage({super.key}); 18 + 19 + @override 20 + State<PhotoLibraryPage> createState() => _PhotoLibraryPageState(); 21 + } 22 + 23 + class _PhotoLibraryPageState extends State<PhotoLibraryPage> { 24 + List<GalleryPhoto> _photos = []; 25 + List<PhotoGroup> _photoGroups = []; 26 + bool _isLoading = true; 27 + String? _error; 28 + final ScrollController _scrollController = ScrollController(); 29 + double _scrollPosition = 0.0; 30 + 31 + @override 32 + void initState() { 33 + super.initState(); 34 + _loadPhotos(); 35 + _scrollController.addListener(_onScroll); 36 + } 37 + 38 + @override 39 + void dispose() { 40 + _scrollController.removeListener(_onScroll); 41 + _scrollController.dispose(); 42 + super.dispose(); 43 + } 44 + 45 + void _onScroll() { 46 + if (_scrollController.hasClients) { 47 + setState(() { 48 + _scrollPosition = _scrollController.offset; 49 + }); 50 + } 51 + } 52 + 53 + // Calculate which group is currently in view based on scroll position 54 + int _getCurrentGroupIndex() { 55 + if (!_scrollController.hasClients || _photoGroups.isEmpty) return 0; 56 + 57 + final scrollOffset = _scrollController.offset; 58 + final padding = 16.0; // ListView padding 59 + double currentOffset = padding; 60 + 61 + for (int i = 0; i < _photoGroups.length; i++) { 62 + final group = _photoGroups[i]; 63 + 64 + // Add space for group title 65 + final titleHeight = 24.0 + 12.0 + (i == 0 ? 0 : 24.0); // title + padding + top margin 66 + currentOffset += titleHeight; 67 + 68 + // Calculate grid height for this group 69 + final photos = group.photos; 70 + final crossAxisCount = photos.length == 1 ? 1 : (photos.length == 2 ? 2 : 3); 71 + final aspectRatio = photos.length <= 2 ? 1.5 : 1.0; 72 + final rows = (photos.length / crossAxisCount).ceil(); 73 + 74 + // Estimate grid item size based on screen width 75 + final screenWidth = MediaQuery.of(context).size.width; 76 + final gridPadding = 30.0 + 32.0; // right padding + left/right margins 77 + final availableWidth = screenWidth - gridPadding; 78 + final itemWidth = (availableWidth - (crossAxisCount - 1) * 4) / crossAxisCount; 79 + final itemHeight = itemWidth / aspectRatio; 80 + final gridHeight = rows * itemHeight + (rows - 1) * 4; // include spacing 81 + 82 + currentOffset += gridHeight; 83 + 84 + // Check if we're currently viewing this group 85 + if (scrollOffset < currentOffset) { 86 + return i; 87 + } 88 + } 89 + 90 + return _photoGroups.length - 1; // Return last group if we're at the bottom 91 + } 92 + 93 + Future<void> _loadPhotos() async { 94 + setState(() { 95 + _isLoading = true; 96 + _error = null; 97 + }); 98 + 99 + try { 100 + final currentUser = apiService.currentUser; 101 + if (currentUser == null || currentUser.did.isEmpty) { 102 + setState(() { 103 + _error = 'No current user found'; 104 + _isLoading = false; 105 + }); 106 + return; 107 + } 108 + 109 + final photos = await apiService.fetchActorPhotos(did: currentUser.did); 110 + 111 + if (mounted) { 112 + setState(() { 113 + _photos = photos; 114 + _photoGroups = _groupPhotosByDate(photos); 115 + _isLoading = false; 116 + }); 117 + 118 + // Force update scroll indicator after layout is complete 119 + WidgetsBinding.instance.addPostFrameCallback((_) { 120 + if (_scrollController.hasClients && mounted) { 121 + setState(() { 122 + _scrollPosition = _scrollController.offset; 123 + }); 124 + } 125 + }); 126 + } 127 + } catch (e) { 128 + if (mounted) { 129 + setState(() { 130 + _error = 'Failed to load photos: $e'; 131 + _isLoading = false; 132 + }); 133 + } 134 + } 135 + } 136 + 137 + List<PhotoGroup> _groupPhotosByDate(List<GalleryPhoto> photos) { 138 + final now = DateTime.now(); 139 + final today = DateTime(now.year, now.month, now.day); 140 + final yesterday = today.subtract(const Duration(days: 1)); 141 + 142 + final Map<String, List<GalleryPhoto>> groupedPhotos = {}; 143 + final List<GalleryPhoto> noExifPhotos = []; 144 + 145 + for (final photo in photos) { 146 + DateTime? photoDate; 147 + // Try to parse the dateTimeOriginal from EXIF record data 148 + if (photo.exif?.record?['dateTimeOriginal'] != null) { 149 + try { 150 + final dateTimeOriginal = photo.exif!.record!['dateTimeOriginal'] as String; 151 + photoDate = DateTime.parse(dateTimeOriginal); 152 + } catch (e) { 153 + // If parsing fails, add to no EXIF group 154 + noExifPhotos.add(photo); 155 + continue; 156 + } 157 + } else { 158 + noExifPhotos.add(photo); 159 + continue; 160 + } 161 + 162 + final photoDay = DateTime(photoDate.year, photoDate.month, photoDate.day); 163 + String groupKey; 164 + 165 + if (photoDay.isAtSameMomentAs(today)) { 166 + groupKey = 'Today'; 167 + } else if (photoDay.isAtSameMomentAs(yesterday)) { 168 + groupKey = 'Yesterday'; 169 + } else { 170 + final daysDifference = today.difference(photoDay).inDays; 171 + 172 + if (daysDifference <= 30) { 173 + // Group by week for last 30 days 174 + final weekStart = photoDay.subtract(Duration(days: photoDay.weekday - 1)); 175 + groupKey = 'Week of ${_formatDate(weekStart)}'; 176 + } else { 177 + // Group by month for older photos 178 + groupKey = '${_getMonthName(photoDate.month)} ${photoDate.year}'; 179 + } 180 + } 181 + 182 + groupedPhotos.putIfAbsent(groupKey, () => []).add(photo); 183 + } 184 + 185 + final List<PhotoGroup> groups = []; 186 + 187 + // Sort and create PhotoGroup objects 188 + final sortedEntries = groupedPhotos.entries.toList() 189 + ..sort((a, b) { 190 + final aDate = _getGroupSortDate(a.key, a.value); 191 + final bDate = _getGroupSortDate(b.key, b.value); 192 + return bDate.compareTo(aDate); // Most recent first 193 + }); 194 + 195 + for (final entry in sortedEntries) { 196 + final sortedPhotos = entry.value 197 + ..sort((a, b) { 198 + final aDate = _getPhotoDate(a); 199 + final bDate = _getPhotoDate(b); 200 + return bDate.compareTo(aDate); // Most recent first within group 201 + }); 202 + 203 + groups.add( 204 + PhotoGroup( 205 + title: entry.key, 206 + photos: sortedPhotos, 207 + sortDate: _getGroupSortDate(entry.key, entry.value), 208 + ), 209 + ); 210 + } 211 + 212 + // Add photos without EXIF data at the end 213 + if (noExifPhotos.isNotEmpty) { 214 + groups.add( 215 + PhotoGroup( 216 + title: 'Photos without date info', 217 + photos: noExifPhotos, 218 + sortDate: DateTime(1970), // Very old date to sort at bottom 219 + ), 220 + ); 221 + } 222 + 223 + return groups; 224 + } 225 + 226 + DateTime _getGroupSortDate(String groupKey, List<GalleryPhoto> photos) { 227 + if (groupKey == 'Today') return DateTime.now(); 228 + if (groupKey == 'Yesterday') return DateTime.now().subtract(const Duration(days: 1)); 229 + 230 + // For other groups, use the most recent photo date in the group 231 + DateTime? latestDate; 232 + for (final photo in photos) { 233 + final photoDate = _getPhotoDate(photo); 234 + if (latestDate == null || photoDate.isAfter(latestDate)) { 235 + latestDate = photoDate; 236 + } 237 + } 238 + return latestDate ?? DateTime(1970); 239 + } 240 + 241 + DateTime _getPhotoDate(GalleryPhoto photo) { 242 + if (photo.exif?.record?['dateTimeOriginal'] != null) { 243 + try { 244 + final dateTimeOriginal = photo.exif!.record!['dateTimeOriginal'] as String; 245 + return DateTime.parse(dateTimeOriginal); 246 + } catch (e) { 247 + // Fall back to a very old date if parsing fails 248 + return DateTime(1970); 249 + } 250 + } 251 + return DateTime(1970); 252 + } 253 + 254 + String _formatDate(DateTime date) { 255 + const months = [ 256 + 'Jan', 257 + 'Feb', 258 + 'Mar', 259 + 'Apr', 260 + 'May', 261 + 'Jun', 262 + 'Jul', 263 + 'Aug', 264 + 'Sep', 265 + 'Oct', 266 + 'Nov', 267 + 'Dec', 268 + ]; 269 + return '${months[date.month - 1]} ${date.day}'; 270 + } 271 + 272 + String _getMonthName(int month) { 273 + const months = [ 274 + 'January', 275 + 'February', 276 + 'March', 277 + 'April', 278 + 'May', 279 + 'June', 280 + 'July', 281 + 'August', 282 + 'September', 283 + 'October', 284 + 'November', 285 + 'December', 286 + ]; 287 + return months[month - 1]; 288 + } 289 + 290 + Future<void> _onRefresh() async { 291 + await _loadPhotos(); 292 + } 293 + 294 + void _showPhotoDetail(GalleryPhoto photo) { 295 + // Create a flattened list of photos in the same order they appear on the page 296 + final List<GalleryPhoto> orderedPhotos = []; 297 + for (final group in _photoGroups) { 298 + orderedPhotos.addAll(group.photos); 299 + } 300 + 301 + // Find the index of the photo in the ordered list 302 + final photoIndex = orderedPhotos.indexOf(photo); 303 + if (photoIndex == -1) return; // Photo not found, shouldn't happen 304 + 305 + Navigator.of(context).push( 306 + PageRouteBuilder( 307 + pageBuilder: (context, animation, secondaryAnimation) => GalleryPhotoView( 308 + photos: orderedPhotos, 309 + initialIndex: photoIndex, 310 + showAddCommentButton: false, 311 + onClose: () => Navigator.of(context).pop(), 312 + ), 313 + transitionDuration: const Duration(milliseconds: 200), 314 + reverseTransitionDuration: const Duration(milliseconds: 200), 315 + transitionsBuilder: (context, animation, secondaryAnimation, child) { 316 + return FadeTransition(opacity: animation, child: child); 317 + }, 318 + ), 319 + ); 320 + } 321 + 322 + @override 323 + Widget build(BuildContext context) { 324 + final theme = Theme.of(context); 325 + 326 + return Scaffold( 327 + backgroundColor: theme.scaffoldBackgroundColor, 328 + appBar: AppBar( 329 + title: const Text('Photo Library'), 330 + backgroundColor: theme.appBarTheme.backgroundColor, 331 + surfaceTintColor: theme.appBarTheme.backgroundColor, 332 + elevation: 0, 333 + ), 334 + body: RefreshIndicator(onRefresh: _onRefresh, child: _buildBodyWithScrollbar(theme)), 335 + ); 336 + } 337 + 338 + Widget _buildBodyWithScrollbar(ThemeData theme) { 339 + return Stack( 340 + children: [ 341 + Padding( 342 + padding: const EdgeInsets.only(right: 30), // Make room for scroll indicator 343 + child: _buildBody(theme), 344 + ), 345 + if (!_isLoading && _error == null && _photos.isNotEmpty) _buildScrollIndicator(theme), 346 + ], 347 + ); 348 + } 349 + 350 + Widget _buildScrollIndicator(ThemeData theme) { 351 + return Positioned( 352 + right: 4, 353 + top: 0, 354 + bottom: 0, 355 + child: GestureDetector( 356 + onPanUpdate: (details) { 357 + if (_scrollController.hasClients) { 358 + final RenderBox renderBox = context.findRenderObject() as RenderBox; 359 + final localPosition = renderBox.globalToLocal(details.globalPosition); 360 + final screenHeight = renderBox.size.height; 361 + final maxScrollExtent = _scrollController.position.maxScrollExtent; 362 + final relativePosition = (localPosition.dy / screenHeight).clamp(0.0, 1.0); 363 + final newPosition = relativePosition * maxScrollExtent; 364 + _scrollController.jumpTo(newPosition.clamp(0.0, maxScrollExtent)); 365 + } 366 + }, 367 + onTapDown: (details) { 368 + if (_scrollController.hasClients) { 369 + final RenderBox renderBox = context.findRenderObject() as RenderBox; 370 + final localPosition = renderBox.globalToLocal(details.globalPosition); 371 + final screenHeight = renderBox.size.height; 372 + final maxScrollExtent = _scrollController.position.maxScrollExtent; 373 + final relativePosition = (localPosition.dy / screenHeight).clamp(0.0, 1.0); 374 + final newPosition = relativePosition * maxScrollExtent; 375 + _scrollController.animateTo( 376 + newPosition.clamp(0.0, maxScrollExtent), 377 + duration: const Duration(milliseconds: 200), 378 + curve: Curves.easeInOut, 379 + ); 380 + } 381 + }, 382 + child: Container( 383 + width: 24, 384 + decoration: BoxDecoration( 385 + color: theme.scaffoldBackgroundColor.withValues(alpha: 0.8), 386 + borderRadius: BorderRadius.circular(12), 387 + ), 388 + child: CustomPaint( 389 + painter: ScrollIndicatorPainter( 390 + scrollPosition: _scrollPosition, 391 + maxScrollExtent: _scrollController.hasClients 392 + ? _scrollController.position.maxScrollExtent 393 + : 0, 394 + viewportHeight: _scrollController.hasClients 395 + ? _scrollController.position.viewportDimension 396 + : 0, 397 + color: theme.colorScheme.onSurface.withValues(alpha: 0.4), 398 + activeColor: theme.colorScheme.primary, 399 + currentGroupIndex: _getCurrentGroupIndex(), 400 + totalGroups: _photoGroups.length, 401 + ), 402 + ), 403 + ), 404 + ), 405 + ); 406 + } 407 + 408 + Widget _buildBody(ThemeData theme) { 409 + if (_isLoading) { 410 + return const Center(child: CircularProgressIndicator()); 411 + } 412 + 413 + if (_error != null) { 414 + return Center( 415 + child: Column( 416 + mainAxisAlignment: MainAxisAlignment.center, 417 + children: [ 418 + Icon(AppIcons.brokenImage, size: 64, color: theme.hintColor), 419 + const SizedBox(height: 16), 420 + Text( 421 + _error!, 422 + style: theme.textTheme.bodyLarge?.copyWith(color: theme.hintColor), 423 + textAlign: TextAlign.center, 424 + ), 425 + const SizedBox(height: 16), 426 + ElevatedButton(onPressed: _loadPhotos, child: const Text('Retry')), 427 + ], 428 + ), 429 + ); 430 + } 431 + 432 + if (_photos.isEmpty) { 433 + return Center( 434 + child: Column( 435 + mainAxisAlignment: MainAxisAlignment.center, 436 + children: [ 437 + Icon(AppIcons.photoLibrary, size: 64, color: theme.hintColor), 438 + const SizedBox(height: 16), 439 + Text( 440 + 'No photos yet', 441 + style: theme.textTheme.headlineSmall?.copyWith(color: theme.hintColor), 442 + ), 443 + const SizedBox(height: 8), 444 + Text( 445 + 'Upload some photos to see them here', 446 + style: theme.textTheme.bodyLarge?.copyWith(color: theme.hintColor), 447 + textAlign: TextAlign.center, 448 + ), 449 + ], 450 + ), 451 + ); 452 + } 453 + 454 + return ListView.builder( 455 + controller: _scrollController, 456 + padding: const EdgeInsets.all(16), 457 + itemCount: _photoGroups.length, 458 + itemBuilder: (context, index) { 459 + final group = _photoGroups[index]; 460 + return _buildPhotoGroup(group, theme, index); 461 + }, 462 + ); 463 + } 464 + 465 + Widget _buildPhotoGroup(PhotoGroup group, ThemeData theme, int index) { 466 + return Column( 467 + crossAxisAlignment: CrossAxisAlignment.start, 468 + children: [ 469 + Padding( 470 + padding: EdgeInsets.only(bottom: 12, top: index == 0 ? 0 : 24), 471 + child: Text( 472 + group.title, 473 + style: theme.textTheme.headlineSmall?.copyWith( 474 + fontWeight: FontWeight.bold, 475 + color: theme.colorScheme.onSurface, 476 + ), 477 + ), 478 + ), 479 + _buildPhotoGrid(group.photos, theme), 480 + ], 481 + ); 482 + } 483 + 484 + Widget _buildPhotoGrid(List<GalleryPhoto> photos, ThemeData theme) { 485 + final crossAxisCount = photos.length == 1 ? 1 : (photos.length == 2 ? 2 : 3); 486 + final aspectRatio = photos.length <= 2 ? 1.5 : 1.0; 487 + 488 + return GridView.builder( 489 + shrinkWrap: true, 490 + physics: const NeverScrollableScrollPhysics(), 491 + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 492 + crossAxisCount: crossAxisCount, 493 + crossAxisSpacing: 4, 494 + mainAxisSpacing: 4, 495 + childAspectRatio: aspectRatio, 496 + ), 497 + itemCount: photos.length, 498 + itemBuilder: (context, index) { 499 + final photo = photos[index]; 500 + return _buildPhotoTile(photo, theme); 501 + }, 502 + ); 503 + } 504 + 505 + Widget _buildPhotoTile(GalleryPhoto photo, ThemeData theme) { 506 + return GestureDetector( 507 + onTap: () => _showPhotoDetail(photo), 508 + child: Hero( 509 + tag: 'photo-${photo.uri}', 510 + child: Container( 511 + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), color: theme.cardColor), 512 + clipBehavior: Clip.antiAlias, 513 + child: AppImage( 514 + url: photo.thumb ?? photo.fullsize, 515 + fit: BoxFit.cover, 516 + width: double.infinity, 517 + height: double.infinity, 518 + placeholder: Container( 519 + color: theme.hintColor.withValues(alpha: 0.1), 520 + child: Icon(AppIcons.photo, color: theme.hintColor, size: 32), 521 + ), 522 + errorWidget: Container( 523 + color: theme.hintColor.withValues(alpha: 0.1), 524 + child: Icon(AppIcons.brokenImage, color: theme.hintColor, size: 32), 525 + ), 526 + ), 527 + ), 528 + ), 529 + ); 530 + } 531 + } 532 + 533 + class ScrollIndicatorPainter extends CustomPainter { 534 + final double scrollPosition; 535 + final double maxScrollExtent; 536 + final double viewportHeight; 537 + final Color color; 538 + final Color activeColor; 539 + final int currentGroupIndex; 540 + final int totalGroups; 541 + 542 + ScrollIndicatorPainter({ 543 + required this.scrollPosition, 544 + required this.maxScrollExtent, 545 + required this.viewportHeight, 546 + required this.color, 547 + required this.activeColor, 548 + required this.currentGroupIndex, 549 + required this.totalGroups, 550 + }); 551 + 552 + @override 553 + void paint(Canvas canvas, Size size) { 554 + const dashCount = 60; // Number of dashes to show (doubled from 30) 555 + const dashHeight = 2.0; // Height when vertical (now width) 556 + const dashWidth = 12.0; // Width when vertical (now height) 557 + 558 + // Calculate spacing to fill the full height 559 + final availableHeight = size.height; 560 + final totalDashHeight = dashCount * dashHeight; 561 + final totalSpacing = availableHeight - totalDashHeight; 562 + final dashSpacing = totalSpacing / (dashCount - 1); 563 + 564 + // Calculate which dash should be active based on current group and total groups 565 + int activeDashIndex; 566 + if (totalGroups > 0) { 567 + // Map current group to dash index (more accurate than scroll position) 568 + final groupProgress = currentGroupIndex / (totalGroups - 1).clamp(1, totalGroups); 569 + activeDashIndex = (groupProgress * (dashCount - 1)).round().clamp(0, dashCount - 1); 570 + } else { 571 + // Fallback to scroll position if no groups 572 + final scrollProgress = maxScrollExtent > 0 573 + ? (scrollPosition / maxScrollExtent).clamp(0.0, 1.0) 574 + : 0.0; 575 + activeDashIndex = (scrollProgress * (dashCount - 1)).round(); 576 + } 577 + 578 + for (int i = 0; i < dashCount; i++) { 579 + final y = i * (dashHeight + dashSpacing); 580 + final isActive = i == activeDashIndex; 581 + 582 + final paint = Paint() 583 + ..color = isActive ? activeColor : color 584 + ..style = PaintingStyle.fill; 585 + 586 + // Create vertical dashes (rotated 90 degrees) 587 + final rect = Rect.fromLTWH((size.width - dashWidth) / 2, y, dashWidth, dashHeight); 588 + 589 + canvas.drawRRect(RRect.fromRectAndRadius(rect, const Radius.circular(1)), paint); 590 + } 591 + } 592 + 593 + @override 594 + bool shouldRepaint(ScrollIndicatorPainter oldDelegate) { 595 + return scrollPosition != oldDelegate.scrollPosition || 596 + maxScrollExtent != oldDelegate.maxScrollExtent || 597 + viewportHeight != oldDelegate.viewportHeight || 598 + currentGroupIndex != oldDelegate.currentGroupIndex || 599 + totalGroups != oldDelegate.totalGroups; 600 + } 601 + }
+4 -2
lib/screens/profile_page.dart
··· 47 47 if (!mounted) return; 48 48 if (success) { 49 49 Navigator.of(context).pop(); 50 - if (mounted) setState(() {}); // Force widget rebuild after modal closes 50 + if (mounted) { 51 + setState(() {}); // Force widget rebuild after modal closes 52 + } 51 53 } else { 52 54 if (!mounted) return; 53 55 ScaffoldMessenger.of( ··· 194 196 onPressed: () async { 195 197 await ref 196 198 .read(profileNotifierProvider(profile.did).notifier) 197 - .toggleFollow(apiService.currentUser?.did); 199 + .toggleFollow(profile.did); 198 200 }, 199 201 label: (profile.viewer?.following?.isNotEmpty == true) 200 202 ? 'Following'
+310
lib/utils/facet_utils.dart
··· 1 + import 'package:flutter/gestures.dart'; 2 + import 'package:flutter/material.dart'; 3 + 4 + class FacetRange { 5 + final int start; 6 + final int end; 7 + final String? type; 8 + final Map<String, dynamic> data; 9 + 10 + FacetRange({required this.start, required this.end, required this.type, required this.data}); 11 + } 12 + 13 + class ProcessedSpan { 14 + final int start; 15 + final int end; 16 + final TextSpan span; 17 + 18 + ProcessedSpan({required this.start, required this.end, required this.span}); 19 + } 20 + 21 + class FacetUtils { 22 + /// Processes facets and returns a list of TextSpans with proper highlighting 23 + static List<TextSpan> processFacets({ 24 + required String text, 25 + required List<Map<String, dynamic>>? facets, 26 + required TextStyle? defaultStyle, 27 + required TextStyle? linkStyle, 28 + void Function(String did)? onMentionTap, 29 + void Function(String url)? onLinkTap, 30 + void Function(String tag)? onTagTap, 31 + }) { 32 + if (facets == null || facets.isEmpty) { 33 + return [TextSpan(text: text, style: defaultStyle)]; 34 + } 35 + 36 + // Build a list of all ranges (start, end, type, data) 37 + final List<FacetRange> ranges = facets.map((facet) { 38 + final feature = facet['features']?[0] ?? {}; 39 + final type = feature['\$type'] ?? feature['type']; 40 + return FacetRange( 41 + start: facet['index']?['byteStart'] ?? facet['byteStart'] ?? 0, 42 + end: facet['index']?['byteEnd'] ?? facet['byteEnd'] ?? 0, 43 + type: type, 44 + data: feature, 45 + ); 46 + }).toList(); 47 + 48 + // Sort ranges by the length of their display text (longest first) to avoid overlap issues 49 + ranges.sort((a, b) { 50 + int aLength = a.end - a.start; 51 + int bLength = b.end - b.start; 52 + 53 + // For links, use the length of the text that will actually be found 54 + if (a.type?.contains('link') == true && a.data['uri'] != null) { 55 + final uri = a.data['uri'] as String; 56 + final possibleTexts = [_extractDisplayTextFromUri(uri), _extractDomainOnly(uri), uri]; 57 + // Use the longest text that exists in the original text 58 + for (final testText in possibleTexts) { 59 + if (text.contains(testText)) { 60 + aLength = testText.length; 61 + break; 62 + } 63 + } 64 + } 65 + 66 + if (b.type?.contains('link') == true && b.data['uri'] != null) { 67 + final uri = b.data['uri'] as String; 68 + final possibleTexts = [_extractDisplayTextFromUri(uri), _extractDomainOnly(uri), uri]; 69 + // Use the longest text that exists in the original text 70 + for (final testText in possibleTexts) { 71 + if (text.contains(testText)) { 72 + bLength = testText.length; 73 + break; 74 + } 75 + } 76 + } 77 + 78 + // Sort by length descending, then by start position ascending 79 + final lengthComparison = bLength.compareTo(aLength); 80 + return lengthComparison != 0 ? lengthComparison : a.start.compareTo(b.start); 81 + }); 82 + 83 + final List<ProcessedSpan> processedSpans = <ProcessedSpan>[]; 84 + final Set<int> usedPositions = <int>{}; // Track which character positions are already used 85 + 86 + for (final range in ranges) { 87 + // For links, we need to find the actual text in the original text 88 + // since the facet positions might be based on the full URL with protocol 89 + String? actualContent; 90 + int actualStart = range.start; 91 + int actualEnd = range.end; 92 + 93 + if (range.type?.contains('link') == true && range.data['uri'] != null) { 94 + final uri = range.data['uri'] as String; 95 + 96 + // First, try to use the exact facet positions if they seem valid 97 + if (range.start >= 0 && range.end <= text.length && range.start < range.end) { 98 + final facetText = text.substring(range.start, range.end); 99 + 100 + // Check if the facet text matches any of our expected URL formats 101 + final possibleTexts = [ 102 + _extractDisplayTextFromUri(uri), // Full URL with protocol 103 + _extractDomainOnly(uri), // Just the domain 104 + uri, // Original URI as-is 105 + ]; 106 + 107 + bool facetTextMatches = possibleTexts.any( 108 + (possible) => 109 + facetText == possible || 110 + facetText.contains(possible) || 111 + possible.contains(facetText), 112 + ); 113 + 114 + if (facetTextMatches) { 115 + // Check if this range overlaps with used positions 116 + bool overlaps = false; 117 + for (int i = range.start; i < range.end; i++) { 118 + if (usedPositions.contains(i)) { 119 + overlaps = true; 120 + break; 121 + } 122 + } 123 + 124 + if (!overlaps) { 125 + actualStart = range.start; 126 + actualEnd = range.end; 127 + actualContent = 128 + facetText; // Use exactly what's in the original text at facet position 129 + 130 + // Mark these positions as used 131 + for (int i = actualStart; i < actualEnd; i++) { 132 + usedPositions.add(i); 133 + } 134 + } 135 + } 136 + } 137 + 138 + // If facet positions didn't work, fall back to searching 139 + if (actualContent == null) { 140 + final possibleTexts = [ 141 + _extractDisplayTextFromUri(uri), // Full URL with protocol 142 + _extractDomainOnly(uri), // Just the domain 143 + uri, // Original URI as-is 144 + ]; 145 + 146 + int searchIndex = 0; 147 + bool foundValidMatch = false; 148 + 149 + // Try each possible text representation 150 + for (final searchText in possibleTexts) { 151 + searchIndex = 0; 152 + while (!foundValidMatch) { 153 + final globalIndex = text.indexOf(searchText, searchIndex); 154 + if (globalIndex == -1) break; 155 + 156 + // Check if this range overlaps with any used positions 157 + bool overlaps = false; 158 + for (int i = globalIndex; i < globalIndex + searchText.length; i++) { 159 + if (usedPositions.contains(i)) { 160 + overlaps = true; 161 + break; 162 + } 163 + } 164 + 165 + if (!overlaps) { 166 + actualStart = globalIndex; 167 + actualEnd = globalIndex + searchText.length; 168 + actualContent = searchText; // Use exactly what we found in the text 169 + foundValidMatch = true; 170 + 171 + // Mark these positions as used 172 + for (int i = actualStart; i < actualEnd; i++) { 173 + usedPositions.add(i); 174 + } 175 + break; 176 + } else { 177 + searchIndex = globalIndex + 1; 178 + } 179 + } 180 + if (foundValidMatch) break; 181 + } 182 + } 183 + } 184 + 185 + // Handle other facet types that might have similar issues 186 + if (actualContent == null) { 187 + // Verify the range is within bounds 188 + if (range.start >= 0 && range.end <= text.length && range.start < range.end) { 189 + actualContent = text.substring(range.start, range.end); 190 + actualStart = range.start; 191 + actualEnd = range.end; 192 + 193 + // Check if this overlaps with used positions 194 + bool overlaps = false; 195 + for (int i = actualStart; i < actualEnd; i++) { 196 + if (usedPositions.contains(i)) { 197 + overlaps = true; 198 + break; 199 + } 200 + } 201 + 202 + if (!overlaps) { 203 + // Mark these positions as used 204 + for (int i = actualStart; i < actualEnd; i++) { 205 + usedPositions.add(i); 206 + } 207 + } else { 208 + // Skip overlapping ranges 209 + actualContent = null; 210 + } 211 + } else { 212 + // Skip invalid ranges 213 + continue; 214 + } 215 + } 216 + 217 + if (actualContent != null) { 218 + TextSpan span; 219 + if (range.type?.contains('mention') == true && range.data['did'] != null) { 220 + span = TextSpan( 221 + text: actualContent, 222 + style: linkStyle, 223 + recognizer: TapGestureRecognizer() 224 + ..onTap = onMentionTap != null ? () => onMentionTap(range.data['did']) : null, 225 + ); 226 + } else if (range.type?.contains('link') == true && range.data['uri'] != null) { 227 + span = TextSpan( 228 + text: actualContent, 229 + style: linkStyle, 230 + recognizer: TapGestureRecognizer() 231 + ..onTap = onLinkTap != null ? () => onLinkTap(range.data['uri']) : null, 232 + ); 233 + } else if (range.type?.contains('tag') == true && range.data['tag'] != null) { 234 + span = TextSpan( 235 + text: '#${range.data['tag']}', 236 + style: linkStyle, 237 + recognizer: TapGestureRecognizer() 238 + ..onTap = onTagTap != null ? () => onTagTap(range.data['tag']) : null, 239 + ); 240 + } else { 241 + span = TextSpan(text: actualContent, style: defaultStyle); 242 + } 243 + 244 + processedSpans.add(ProcessedSpan(start: actualStart, end: actualEnd, span: span)); 245 + } 246 + } 247 + 248 + // Sort processed spans by position and build final spans list 249 + processedSpans.sort((a, b) => a.start.compareTo(b.start)); 250 + int pos = 0; 251 + final spans = <TextSpan>[]; 252 + 253 + for (final processedSpan in processedSpans) { 254 + if (processedSpan.start > pos) { 255 + spans.add(TextSpan(text: text.substring(pos, processedSpan.start), style: defaultStyle)); 256 + } 257 + spans.add(processedSpan.span); 258 + pos = processedSpan.end; 259 + } 260 + 261 + if (pos < text.length) { 262 + spans.add(TextSpan(text: text.substring(pos), style: defaultStyle)); 263 + } 264 + 265 + return spans; 266 + } 267 + 268 + /// Extracts the display text from a URI (keeps protocol and domain, removes path) 269 + static String _extractDisplayTextFromUri(String uri) { 270 + // Find the first slash after the protocol to remove the path 271 + String protocolAndDomain = uri; 272 + if (uri.startsWith('https://')) { 273 + final pathIndex = uri.indexOf('/', 8); // Start search after "https://" 274 + if (pathIndex != -1) { 275 + protocolAndDomain = uri.substring(0, pathIndex); 276 + } 277 + } else if (uri.startsWith('http://')) { 278 + final pathIndex = uri.indexOf('/', 7); // Start search after "http://" 279 + if (pathIndex != -1) { 280 + protocolAndDomain = uri.substring(0, pathIndex); 281 + } 282 + } else { 283 + // For URIs without protocol, just remove the path 284 + final slashIndex = uri.indexOf('/'); 285 + if (slashIndex != -1) { 286 + protocolAndDomain = uri.substring(0, slashIndex); 287 + } 288 + } 289 + 290 + return protocolAndDomain; 291 + } 292 + 293 + /// Extracts just the domain part from a URI (removes protocol and path) 294 + static String _extractDomainOnly(String uri) { 295 + String domain = uri; 296 + if (uri.startsWith('https://')) { 297 + domain = uri.substring(8); 298 + } else if (uri.startsWith('http://')) { 299 + domain = uri.substring(7); 300 + } 301 + 302 + // Remove path 303 + final slashIndex = domain.indexOf('/'); 304 + if (slashIndex != -1) { 305 + domain = domain.substring(0, slashIndex); 306 + } 307 + 308 + return domain; 309 + } 310 + }
+12 -14
lib/widgets/add_comment_sheet.dart
··· 5 5 import 'package:grain/app_icons.dart'; 6 6 import 'package:grain/widgets/app_image.dart'; 7 7 import 'package:grain/widgets/gallery_preview.dart'; 8 + import 'package:grain/widgets/faceted_text_field.dart'; 8 9 9 10 Future<void> showAddCommentSheet( 10 11 BuildContext context, { ··· 185 186 child: Column( 186 187 crossAxisAlignment: CrossAxisAlignment.start, 187 188 children: [ 188 - Row( 189 + Column( 190 + crossAxisAlignment: CrossAxisAlignment.start, 189 191 children: [ 190 192 Text( 191 193 creator is Map ··· 199 201 ? (creator['handle'] ?? '') 200 202 : (creator.handle ?? '')) 201 203 .isNotEmpty) ...[ 202 - const SizedBox(width: 8), 204 + const SizedBox(height: 1), 203 205 Text( 204 206 '@${creator is Map ? creator['handle'] : creator.handle}', 205 207 style: theme.textTheme.bodySmall?.copyWith( ··· 315 317 ), 316 318 // Text input 317 319 Expanded( 318 - child: TextField( 319 - controller: widget.controller, 320 - focusNode: _focusNode, 321 - maxLines: 6, 322 - minLines: 2, 323 - style: theme.textTheme.bodyMedium, 324 - decoration: InputDecoration( 320 + child: Padding( 321 + padding: const EdgeInsets.only(left: 10), 322 + child: FacetedTextField( 323 + controller: widget.controller, 324 + maxLines: 6, 325 + enabled: true, 326 + keyboardType: TextInputType.multiline, 325 327 hintText: 'Add a comment', 326 - hintStyle: theme.textTheme.bodyMedium?.copyWith(color: theme.hintColor), 327 - border: InputBorder.none, 328 - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), 329 - isDense: true, 330 - filled: false, 328 + // The FacetedTextField handles its own style and padding internally 331 329 ), 332 330 ), 333 331 ),
+15
lib/widgets/app_drawer.dart
··· 2 2 import 'package:grain/api.dart'; 3 3 import 'package:grain/app_icons.dart'; 4 4 import 'package:grain/screens/log_page.dart'; 5 + import 'package:grain/screens/photo_library_page.dart'; 5 6 import 'package:grain/widgets/app_version_text.dart'; 6 7 7 8 class AppDrawer extends StatelessWidget { ··· 176 177 onTap: () { 177 178 Navigator.pop(context); 178 179 onProfile(); 180 + }, 181 + ), 182 + ListTile( 183 + leading: Icon( 184 + AppIcons.photoLibrary, 185 + size: 18, 186 + color: activeIndex == 4 ? theme.colorScheme.primary : theme.iconTheme.color, 187 + ), 188 + title: const Text('Photo Library'), 189 + onTap: () { 190 + Navigator.pop(context); 191 + Navigator.of( 192 + context, 193 + ).push(MaterialPageRoute(builder: (context) => const PhotoLibraryPage())); 179 194 }, 180 195 ), 181 196 ListTile(
+71 -25
lib/widgets/edit_profile_sheet.dart
··· 4 4 import 'package:flutter/material.dart'; 5 5 import 'package:flutter/services.dart'; 6 6 import 'package:grain/app_icons.dart'; 7 + import 'package:grain/widgets/faceted_text_field.dart'; 7 8 import 'package:grain/widgets/plain_text_field.dart'; 8 9 import 'package:image_picker/image_picker.dart'; 9 10 ··· 61 62 late TextEditingController _descriptionController; 62 63 XFile? _selectedAvatar; 63 64 bool _saving = false; 64 - bool _hasChanged = false; 65 + static const int maxDisplayNameGraphemes = 64; 66 + static const int maxDescriptionGraphemes = 256; 65 67 66 68 @override 67 69 void initState() { ··· 69 71 _displayNameController = TextEditingController(text: widget.initialDisplayName ?? ''); 70 72 _descriptionController = TextEditingController(text: widget.initialDescription ?? ''); 71 73 _displayNameController.addListener(_onInputChanged); 72 - _descriptionController.addListener(_onInputChanged); 74 + _descriptionController.addListener(_onDescriptionChanged); 75 + } 76 + 77 + void _onDescriptionChanged() { 78 + setState(() {}); // For character count 73 79 } 74 80 75 81 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 - } 82 + setState(() { 83 + // Trigger rebuild to update character counts 84 + }); 90 85 } 91 86 92 87 @override 93 88 void dispose() { 94 - _displayNameController.removeListener(_onInputChanged); 95 - _descriptionController.removeListener(_onInputChanged); 96 89 _displayNameController.dispose(); 97 90 _descriptionController.dispose(); 98 91 super.dispose(); ··· 104 97 if (picked != null) { 105 98 setState(() { 106 99 _selectedAvatar = picked; 107 - _onInputChanged(); 108 100 }); 109 101 } 110 102 } ··· 113 105 Widget build(BuildContext context) { 114 106 final theme = Theme.of(context); 115 107 final avatarRadius = 44.0; 108 + final displayNameGraphemes = _displayNameController.text.characters.length; 109 + final descriptionGraphemes = _descriptionController.text.characters.length; 116 110 return CupertinoPageScaffold( 117 111 backgroundColor: theme.colorScheme.surface, 118 112 navigationBar: CupertinoNavigationBar( ··· 132 126 ), 133 127 trailing: CupertinoButton( 134 128 padding: EdgeInsets.zero, 135 - onPressed: (!_hasChanged || _saving) 129 + onPressed: _saving 136 130 ? null 137 131 : () async { 132 + if (displayNameGraphemes > maxDisplayNameGraphemes || 133 + descriptionGraphemes > maxDescriptionGraphemes) { 134 + await showDialog( 135 + context: context, 136 + builder: (context) => AlertDialog( 137 + title: const Text('Character Limit Exceeded'), 138 + content: Text( 139 + displayNameGraphemes > maxDisplayNameGraphemes 140 + ? 'Display Name must be $maxDisplayNameGraphemes characters or fewer.' 141 + : 'Description must be $maxDescriptionGraphemes characters or fewer.', 142 + ), 143 + actions: [ 144 + TextButton( 145 + child: const Text('OK'), 146 + onPressed: () => Navigator.of(context).pop(), 147 + ), 148 + ], 149 + ), 150 + ); 151 + return; 152 + } 138 153 if (widget.onSave != null) { 139 154 setState(() { 140 155 _saving = true; ··· 155 170 Text( 156 171 'Save', 157 172 style: TextStyle( 158 - color: (!_hasChanged || _saving) 159 - ? theme.disabledColor 160 - : theme.colorScheme.primary, 173 + color: _saving ? theme.disabledColor : theme.colorScheme.primary, 161 174 fontWeight: FontWeight.w600, 162 175 ), 163 176 ), ··· 233 246 controller: _displayNameController, 234 247 maxLines: 1, 235 248 ), 249 + Padding( 250 + padding: const EdgeInsets.only(top: 4), 251 + child: Row( 252 + mainAxisAlignment: MainAxisAlignment.spaceBetween, 253 + children: [ 254 + const SizedBox(), 255 + Text( 256 + '$displayNameGraphemes/$maxDisplayNameGraphemes', 257 + style: theme.textTheme.bodySmall?.copyWith( 258 + color: displayNameGraphemes > maxDisplayNameGraphemes 259 + ? theme.colorScheme.error 260 + : theme.textTheme.bodySmall?.color, 261 + ), 262 + ), 263 + ], 264 + ), 265 + ), 236 266 const SizedBox(height: 12), 237 - PlainTextField( 267 + FacetedTextField( 238 268 label: 'Description', 239 269 controller: _descriptionController, 240 270 maxLines: 6, 241 271 ), 272 + Padding( 273 + padding: const EdgeInsets.only(top: 4), 274 + child: Row( 275 + mainAxisAlignment: MainAxisAlignment.spaceBetween, 276 + children: [ 277 + const SizedBox(), 278 + Text( 279 + '$descriptionGraphemes/$maxDescriptionGraphemes', 280 + style: theme.textTheme.bodySmall?.copyWith( 281 + color: descriptionGraphemes > maxDescriptionGraphemes 282 + ? theme.colorScheme.error 283 + : theme.textTheme.bodySmall?.color, 284 + ), 285 + ), 286 + ], 287 + ), 288 + ), 242 289 ], 243 290 ), 244 291 ), 245 292 ), 246 - const SizedBox(height: 24), 247 293 ], 248 294 ), 249 295 ),
+14 -63
lib/widgets/faceted_text.dart
··· 1 - import 'package:flutter/gestures.dart'; 2 1 import 'package:flutter/material.dart'; 2 + 3 + import '../utils/facet_utils.dart'; 3 4 4 5 class FacetedText extends StatelessWidget { 5 6 final String text; ··· 32 33 fontWeight: FontWeight.w600, 33 34 decoration: TextDecoration.underline, 34 35 ); 36 + 35 37 if (facets == null || facets!.isEmpty) { 36 38 return Text(text, style: defaultStyle); 37 39 } 38 - // Build a list of all ranges (start, end, type, data) 39 - final List<_FacetRange> ranges = facets!.map((facet) { 40 - final feature = facet['features']?[0] ?? {}; 41 - final type = feature['\$type'] ?? feature['type']; 42 - return _FacetRange( 43 - start: facet['index']?['byteStart'] ?? facet['byteStart'] ?? 0, 44 - end: facet['index']?['byteEnd'] ?? facet['byteEnd'] ?? 0, 45 - type: type, 46 - data: feature, 47 - ); 48 - }).toList(); 49 - ranges.sort((a, b) => a.start.compareTo(b.start)); 50 - int pos = 0; 51 - final spans = <TextSpan>[]; 52 - for (final range in ranges) { 53 - if (range.start > pos) { 54 - spans.add(TextSpan(text: text.substring(pos, range.start), style: defaultStyle)); 55 - } 56 - final content = text.substring(range.start, range.end); 57 - if (range.type?.contains('mention') == true && range.data['did'] != null) { 58 - spans.add( 59 - TextSpan( 60 - text: content, 61 - style: defaultLinkStyle, 62 - recognizer: TapGestureRecognizer() 63 - ..onTap = onMentionTap != null ? () => onMentionTap!(range.data['did']) : null, 64 - ), 65 - ); 66 - } else if (range.type?.contains('link') == true && range.data['uri'] != null) { 67 - spans.add( 68 - TextSpan( 69 - text: content, 70 - style: defaultLinkStyle, 71 - recognizer: TapGestureRecognizer() 72 - ..onTap = onLinkTap != null ? () => onLinkTap!(range.data['uri']) : null, 73 - ), 74 - ); 75 - } else if (range.type?.contains('tag') == true && range.data['tag'] != null) { 76 - spans.add( 77 - TextSpan( 78 - text: '#${range.data['tag']}', 79 - style: defaultLinkStyle, 80 - recognizer: TapGestureRecognizer() 81 - ..onTap = onTagTap != null ? () => onTagTap!(range.data['tag']) : null, 82 - ), 83 - ); 84 - } else { 85 - spans.add(TextSpan(text: content, style: defaultStyle)); 86 - } 87 - pos = range.end; 88 - } 89 - if (pos < text.length) { 90 - spans.add(TextSpan(text: text.substring(pos), style: defaultStyle)); 91 - } 40 + 41 + final spans = FacetUtils.processFacets( 42 + text: text, 43 + facets: facets, 44 + defaultStyle: defaultStyle, 45 + linkStyle: defaultLinkStyle, 46 + onMentionTap: onMentionTap, 47 + onLinkTap: onLinkTap, 48 + onTagTap: onTagTap, 49 + ); 50 + 92 51 return RichText(text: TextSpan(children: spans)); 93 52 } 94 53 } 95 - 96 - class _FacetRange { 97 - final int start; 98 - final int end; 99 - final String? type; 100 - final Map<String, dynamic> data; 101 - _FacetRange({required this.start, required this.end, required this.type, required this.data}); 102 - }
+473
lib/widgets/faceted_text_field.dart
··· 1 + import 'dart:async'; 2 + 3 + import 'package:bluesky_text/bluesky_text.dart'; 4 + import 'package:flutter/material.dart'; 5 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 + 7 + import '../models/profile.dart'; 8 + import '../providers/actor_search_provider.dart'; 9 + import '../utils/facet_utils.dart'; 10 + 11 + class FacetedTextField extends ConsumerStatefulWidget { 12 + final String? label; 13 + final TextEditingController controller; 14 + final int maxLines; 15 + final bool enabled; 16 + final TextInputType? keyboardType; 17 + final String? hintText; 18 + final void Function(String)? onChanged; 19 + final Widget? prefixIcon; 20 + final Widget? suffixIcon; 21 + final List<Map<String, dynamic>>? facets; 22 + 23 + const FacetedTextField({ 24 + super.key, 25 + this.label, 26 + required this.controller, 27 + this.maxLines = 1, 28 + this.enabled = true, 29 + this.keyboardType, 30 + this.hintText, 31 + this.onChanged, 32 + this.prefixIcon, 33 + this.suffixIcon, 34 + this.facets, 35 + }); 36 + 37 + @override 38 + ConsumerState<FacetedTextField> createState() => _FacetedTextFieldState(); 39 + } 40 + 41 + class _FacetedTextFieldState extends ConsumerState<FacetedTextField> { 42 + // Track which handles have been inserted via overlay selection 43 + final Set<String> _insertedHandles = {}; 44 + OverlayEntry? _overlayEntry; 45 + final GlobalKey _fieldKey = GlobalKey(); 46 + List<Profile> _actorResults = []; 47 + Timer? _debounceTimer; 48 + 49 + @override 50 + void initState() { 51 + super.initState(); 52 + widget.controller.addListener(_onTextChanged); 53 + } 54 + 55 + @override 56 + void dispose() { 57 + widget.controller.removeListener(_onTextChanged); 58 + _debounceTimer?.cancel(); 59 + _removeOverlay(); 60 + super.dispose(); 61 + } 62 + 63 + void _onTextChanged() async { 64 + final text = widget.controller.text; 65 + final selection = widget.controller.selection; 66 + final cursorPos = selection.baseOffset; 67 + if (cursorPos < 0) { 68 + _removeOverlay(); 69 + return; 70 + } 71 + // If the last character typed is a space, always close overlay 72 + if (cursorPos > 0 && text[cursorPos - 1] == ' ') { 73 + _removeOverlay(); 74 + return; 75 + } 76 + // Find the @mention match that contains the cursor 77 + final regex = RegExp(r'@([\w.]+)'); 78 + final matches = regex.allMatches(text); 79 + String? query; 80 + for (final match in matches) { 81 + final start = match.start; 82 + final end = match.end; 83 + if (cursorPos > start && cursorPos <= end) { 84 + query = match.group(1); 85 + break; 86 + } 87 + } 88 + if (query != null && query.isNotEmpty) { 89 + _debounceTimer?.cancel(); 90 + _debounceTimer = Timer(const Duration(milliseconds: 500), () async { 91 + final results = await ref.read(actorSearchProvider.notifier).search(query!); 92 + if (mounted) { 93 + setState(() { 94 + _actorResults = results; 95 + }); 96 + _showOverlay(); 97 + } 98 + }); 99 + return; 100 + } 101 + _debounceTimer?.cancel(); 102 + _removeOverlay(); 103 + } 104 + 105 + void _showOverlay() { 106 + WidgetsBinding.instance.addPostFrameCallback((_) { 107 + _removeOverlay(); 108 + final overlay = Overlay.of(context); 109 + final caretOffset = _getCaretPosition(); 110 + if (caretOffset == null) return; 111 + 112 + // Show only the first 5 results, no scroll, use simple rows 113 + final double rowHeight = 44.0; 114 + final int maxItems = 5; 115 + final resultsToShow = _actorResults.take(maxItems).toList(); 116 + final double overlayHeight = resultsToShow.length * rowHeight; 117 + final double overlayWidth = 300.0; 118 + 119 + // Get screen size 120 + final mediaQuery = MediaQuery.of(context); 121 + final screenWidth = mediaQuery.size.width; 122 + 123 + // Default to left of caret, but if it would overflow, switch to right 124 + double left = caretOffset.dx; 125 + if (left + overlayWidth > screenWidth - 8) { 126 + // Try to align right edge of overlay with caret, but don't go off left edge 127 + left = (caretOffset.dx - overlayWidth).clamp(8.0, screenWidth - overlayWidth - 8.0); 128 + } 129 + 130 + _overlayEntry = OverlayEntry( 131 + builder: (context) => Positioned( 132 + left: left, 133 + top: caretOffset.dy, 134 + width: overlayWidth, 135 + height: overlayHeight, 136 + child: Material( 137 + elevation: 4, 138 + child: Column( 139 + mainAxisSize: MainAxisSize.min, 140 + children: resultsToShow.map((actor) { 141 + return Material( 142 + color: Colors.transparent, 143 + child: InkWell( 144 + onTap: () => _insertActor(actor.handle), 145 + child: Container( 146 + height: rowHeight, 147 + width: double.infinity, 148 + alignment: Alignment.centerLeft, 149 + padding: const EdgeInsets.symmetric(horizontal: 12.0), 150 + child: Row( 151 + children: [ 152 + if (actor.avatar != null && actor.avatar!.isNotEmpty) 153 + CircleAvatar(radius: 16, backgroundImage: NetworkImage(actor.avatar!)) 154 + else 155 + CircleAvatar(radius: 16, child: Icon(Icons.person, size: 16)), 156 + const SizedBox(width: 12), 157 + Expanded( 158 + child: Text( 159 + actor.displayName ?? actor.handle, 160 + style: Theme.of(context).textTheme.bodyMedium, 161 + overflow: TextOverflow.ellipsis, 162 + ), 163 + ), 164 + const SizedBox(width: 8), 165 + Text( 166 + '@${actor.handle}', 167 + style: Theme.of( 168 + context, 169 + ).textTheme.bodySmall?.copyWith(color: Colors.grey[600]), 170 + overflow: TextOverflow.ellipsis, 171 + ), 172 + ], 173 + ), 174 + ), 175 + ), 176 + ); 177 + }).toList(), 178 + ), 179 + ), 180 + ), 181 + ); 182 + overlay.insert(_overlayEntry!); 183 + }); 184 + } 185 + 186 + void _removeOverlay() { 187 + if (_overlayEntry != null) { 188 + _overlayEntry?.remove(); 189 + _overlayEntry = null; 190 + } 191 + } 192 + 193 + Offset? _getCaretPosition() { 194 + final renderBox = _fieldKey.currentContext?.findRenderObject() as RenderBox?; 195 + if (renderBox == null) return null; 196 + 197 + final controller = widget.controller; 198 + final selection = controller.selection; 199 + if (!selection.isValid) return null; 200 + 201 + // Get the text up to the caret 202 + final text = controller.text.substring(0, selection.baseOffset); 203 + final textStyle = 204 + Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 15) ?? 205 + const TextStyle(fontSize: 15); 206 + final textPainter = TextPainter( 207 + text: TextSpan(text: text, style: textStyle), 208 + textDirection: TextDirection.ltr, 209 + maxLines: widget.maxLines, 210 + ); 211 + textPainter.layout(minWidth: 0, maxWidth: renderBox.size.width); 212 + 213 + final caretOffset = textPainter.getOffsetForCaret(TextPosition(offset: text.length), Rect.zero); 214 + 215 + // Convert caret offset to global coordinates 216 + final fieldOffset = renderBox.localToGlobal(Offset.zero); 217 + // Add vertical padding to position below the caret 218 + return fieldOffset + Offset(caretOffset.dx, caretOffset.dy + textPainter.preferredLineHeight); 219 + } 220 + 221 + void _insertActor(String actorName) { 222 + final text = widget.controller.text; 223 + final selection = widget.controller.selection; 224 + final cursorPos = selection.baseOffset; 225 + // Find the @mention match that contains the cursor (not just before it) 226 + final regex = RegExp(r'@([\w.]+)'); 227 + final matches = regex.allMatches(text); 228 + Match? matchToReplace; 229 + for (final match in matches) { 230 + if (cursorPos > match.start && cursorPos <= match.end) { 231 + matchToReplace = match; 232 + break; 233 + } 234 + } 235 + if (matchToReplace != null) { 236 + final start = matchToReplace.start; 237 + final end = matchToReplace.end; 238 + final newText = text.replaceRange(start, end, '@$actorName '); 239 + setState(() { 240 + _insertedHandles.add(actorName); 241 + }); 242 + widget.controller.value = TextEditingValue( 243 + text: newText, 244 + selection: TextSelection.collapsed(offset: start + actorName.length + 2), 245 + ); 246 + } 247 + _removeOverlay(); 248 + } 249 + 250 + @override 251 + Widget build(BuildContext context) { 252 + final theme = Theme.of(context); 253 + return Column( 254 + crossAxisAlignment: CrossAxisAlignment.start, 255 + children: [ 256 + if (widget.label != null && widget.label!.isNotEmpty) ...[ 257 + Text( 258 + widget.label!, 259 + style: theme.textTheme.bodyMedium?.copyWith( 260 + fontWeight: FontWeight.w500, 261 + color: theme.colorScheme.onSurface, 262 + ), 263 + ), 264 + const SizedBox(height: 6), 265 + ], 266 + Container( 267 + decoration: BoxDecoration( 268 + color: theme.brightness == Brightness.dark ? Colors.grey[850] : Colors.grey[300], 269 + borderRadius: BorderRadius.circular(8), 270 + ), 271 + child: Focus( 272 + child: Builder( 273 + builder: (context) { 274 + final isFocused = Focus.of(context).hasFocus; 275 + return Stack( 276 + children: [ 277 + _MentionHighlightTextField( 278 + key: _fieldKey, 279 + controller: widget.controller, 280 + maxLines: widget.maxLines, 281 + enabled: widget.enabled, 282 + keyboardType: widget.keyboardType, 283 + onChanged: widget.onChanged, 284 + hintText: widget.hintText, 285 + prefixIcon: widget.prefixIcon, 286 + suffixIcon: widget.suffixIcon, 287 + insertedHandles: _insertedHandles, 288 + facets: widget.facets, 289 + ), 290 + // Border overlay 291 + Positioned.fill( 292 + child: IgnorePointer( 293 + child: AnimatedContainer( 294 + duration: const Duration(milliseconds: 150), 295 + decoration: BoxDecoration( 296 + border: Border.all( 297 + color: isFocused ? theme.colorScheme.primary : theme.dividerColor, 298 + width: isFocused ? 2 : 0, 299 + ), 300 + borderRadius: BorderRadius.circular(8), 301 + ), 302 + ), 303 + ), 304 + ), 305 + ], 306 + ); 307 + }, 308 + ), 309 + ), 310 + ), 311 + ], 312 + ); 313 + } 314 + } 315 + 316 + class _MentionHighlightTextField extends StatefulWidget { 317 + final Set<String>? insertedHandles; 318 + final TextEditingController controller; 319 + final int maxLines; 320 + final bool enabled; 321 + final TextInputType? keyboardType; 322 + final String? hintText; 323 + final void Function(String)? onChanged; 324 + final Widget? prefixIcon; 325 + final Widget? suffixIcon; 326 + final List<Map<String, dynamic>>? facets; 327 + 328 + const _MentionHighlightTextField({ 329 + super.key, 330 + required this.controller, 331 + required this.maxLines, 332 + required this.enabled, 333 + this.keyboardType, 334 + this.hintText, 335 + this.onChanged, 336 + this.prefixIcon, 337 + this.suffixIcon, 338 + this.insertedHandles, 339 + this.facets, 340 + }); 341 + 342 + @override 343 + State<_MentionHighlightTextField> createState() => _MentionHighlightTextFieldState(); 344 + } 345 + 346 + class _MentionHighlightTextFieldState extends State<_MentionHighlightTextField> { 347 + final ScrollController _richTextScrollController = ScrollController(); 348 + final ScrollController _textFieldScrollController = ScrollController(); 349 + 350 + void _onMentionTap(String did) { 351 + // Show overlay for this mention (simulate as if user is typing @mention) 352 + final parent = context.findAncestorStateOfType<_FacetedTextFieldState>(); 353 + if (parent != null) { 354 + parent._showOverlay(); 355 + } 356 + } 357 + 358 + List<Map<String, dynamic>> _parsedFacets = []; 359 + Timer? _facetDebounce; 360 + 361 + @override 362 + void initState() { 363 + super.initState(); 364 + _parseFacets(); 365 + widget.controller.addListener(_parseFacets); 366 + 367 + // Sync scroll controllers 368 + _textFieldScrollController.addListener(() { 369 + if (_richTextScrollController.hasClients && _textFieldScrollController.hasClients) { 370 + _richTextScrollController.jumpTo(_textFieldScrollController.offset); 371 + } 372 + }); 373 + } 374 + 375 + @override 376 + void dispose() { 377 + widget.controller.removeListener(_parseFacets); 378 + _facetDebounce?.cancel(); 379 + _richTextScrollController.dispose(); 380 + _textFieldScrollController.dispose(); 381 + super.dispose(); 382 + } 383 + 384 + void _parseFacets() { 385 + _facetDebounce?.cancel(); 386 + _facetDebounce = Timer(const Duration(milliseconds: 100), () async { 387 + final text = widget.controller.text; 388 + if (widget.facets != null && widget.facets!.isNotEmpty) { 389 + setState(() => _parsedFacets = widget.facets!); 390 + } else { 391 + try { 392 + final blueskyText = BlueskyText(text); 393 + final entities = blueskyText.entities; 394 + final facets = await entities.toFacets(); 395 + if (mounted) setState(() => _parsedFacets = List<Map<String, dynamic>>.from(facets)); 396 + } catch (_) { 397 + if (mounted) setState(() => _parsedFacets = []); 398 + } 399 + } 400 + }); 401 + } 402 + 403 + @override 404 + Widget build(BuildContext context) { 405 + final theme = Theme.of(context); 406 + final text = widget.controller.text; 407 + final baseStyle = theme.textTheme.bodyMedium?.copyWith(fontSize: 15); 408 + final linkStyle = baseStyle?.copyWith(color: theme.colorScheme.primary); 409 + 410 + // Use the same facet processing logic as FacetedText 411 + final spans = FacetUtils.processFacets( 412 + text: text, 413 + facets: _parsedFacets, 414 + defaultStyle: baseStyle, 415 + linkStyle: linkStyle, 416 + onMentionTap: _onMentionTap, 417 + onLinkTap: null, // No link tap in text field 418 + onTagTap: null, // No tag tap in text field 419 + ); 420 + return LayoutBuilder( 421 + builder: (context, constraints) { 422 + return SizedBox( 423 + width: double.infinity, // Make it full width 424 + height: widget.maxLines == 1 425 + ? null 426 + : (baseStyle?.fontSize ?? 15) * 1.4 * widget.maxLines + 427 + 24, // Line height * maxLines + padding 428 + child: Stack( 429 + children: [ 430 + // RichText for highlight wrapped in SingleChildScrollView 431 + SingleChildScrollView( 432 + controller: _richTextScrollController, 433 + physics: const NeverScrollableScrollPhysics(), // Disable direct interaction 434 + child: Padding( 435 + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), 436 + child: RichText( 437 + text: TextSpan(children: spans), 438 + maxLines: null, // Allow unlimited lines for scrolling 439 + overflow: TextOverflow.visible, 440 + ), 441 + ), 442 + ), 443 + // Editable TextField for input, but with transparent text so only RichText is visible 444 + Positioned.fill( 445 + child: TextField( 446 + controller: widget.controller, 447 + scrollController: _textFieldScrollController, 448 + maxLines: null, // Allow unlimited lines for scrolling 449 + enabled: widget.enabled, 450 + keyboardType: widget.keyboardType, 451 + onChanged: widget.onChanged, 452 + style: baseStyle?.copyWith(color: const Color(0x01000000)), 453 + cursorColor: theme.colorScheme.primary, 454 + showCursor: true, 455 + enableInteractiveSelection: true, 456 + decoration: InputDecoration( 457 + hintText: widget.hintText, 458 + hintStyle: baseStyle?.copyWith(color: theme.hintColor), 459 + border: InputBorder.none, 460 + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), 461 + isDense: true, 462 + prefixIcon: widget.prefixIcon, 463 + suffixIcon: widget.suffixIcon, 464 + ), 465 + ), 466 + ), 467 + ], 468 + ), 469 + ); 470 + }, 471 + ); 472 + } 473 + }
+20 -1
lib/widgets/gallery_photo_view.dart
··· 128 128 final gallery = widget.gallery; 129 129 final subject = gallery?.uri; 130 130 final focus = photo.uri; 131 - if (subject == null || focus == null) { 131 + if (subject == null) { 132 132 return; 133 133 } 134 134 // Use the provider's createComment method ··· 165 165 }, 166 166 ), 167 167 ], 168 + ), 169 + ), 170 + ), 171 + if (!widget.showAddCommentButton && photo.exif != null) 172 + SafeArea( 173 + top: false, 174 + child: Padding( 175 + padding: const EdgeInsets.all(16), 176 + child: Align( 177 + alignment: Alignment.centerRight, 178 + child: IconButton( 179 + icon: Icon(Icons.camera_alt, color: Colors.white), 180 + onPressed: () { 181 + showDialog( 182 + context: context, 183 + builder: (context) => PhotoExifDialog(exif: photo.exif!), 184 + ); 185 + }, 186 + ), 168 187 ), 169 188 ), 170 189 ),
+44 -23
lib/widgets/timeline_item.dart
··· 8 8 import 'package:grain/widgets/faceted_text.dart'; 9 9 import 'package:grain/widgets/gallery_action_buttons.dart'; 10 10 import 'package:grain/widgets/gallery_preview.dart'; 11 + import 'package:url_launcher/url_launcher.dart'; 11 12 12 13 import '../providers/gallery_cache_provider.dart'; 13 14 import '../screens/gallery_page.dart'; ··· 73 74 mainAxisAlignment: MainAxisAlignment.spaceBetween, 74 75 children: [ 75 76 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, 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 + ), 85 100 ), 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, 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 + ), 96 111 ), 97 - ), 98 - ], 112 + ], 113 + ), 114 + overflow: TextOverflow.ellipsis, 115 + maxLines: 1, 99 116 ), 100 - overflow: TextOverflow.ellipsis, 101 - maxLines: 1, 102 117 ), 103 118 ), 104 119 Text( ··· 159 174 context, 160 175 MaterialPageRoute(builder: (_) => HashtagPage(hashtag: tag)), 161 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 + }, 162 183 ), 163 184 ), 164 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 + }
+8 -8
pubspec.lock
··· 149 149 dependency: transitive 150 150 description: 151 151 name: built_value 152 - sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" 152 + sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62" 153 153 url: "https://pub.dev" 154 154 source: hosted 155 - version: "8.10.1" 155 + version: "8.11.0" 156 156 cached_network_image: 157 157 dependency: "direct main" 158 158 description: ··· 309 309 dependency: transitive 310 310 description: 311 311 name: dart_style 312 - sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" 312 + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" 313 313 url: "https://pub.dev" 314 314 source: hosted 315 - version: "3.1.0" 315 + version: "3.1.1" 316 316 desktop_webview_window: 317 317 dependency: transitive 318 318 description: ··· 620 620 dependency: transitive 621 621 description: 622 622 name: image_picker_android 623 - sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" 623 + sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d" 624 624 url: "https://pub.dev" 625 625 source: hosted 626 - version: "0.8.12+23" 626 + version: "0.8.12+24" 627 627 image_picker_for_web: 628 628 dependency: transitive 629 629 description: ··· 748 748 dependency: "direct main" 749 749 description: 750 750 name: logger 751 - sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999" 751 + sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" 752 752 url: "https://pub.dev" 753 753 source: hosted 754 - version: "2.6.0" 754 + version: "2.6.1" 755 755 logging: 756 756 dependency: transitive 757 757 description:
+2 -2
pubspec.yaml
··· 1 1 name: grain 2 - description: "A new Flutter project." 2 + description: "Grain Social Mobile App" 3 3 # The following line prevents the package from being accidentally published to 4 4 # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 5 publish_to: "none" # Remove this line if you wish to publish to pub.dev ··· 16 16 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 17 # In Windows, build-name is used as the major, minor, and patch parts 18 18 # of the product and file versions while build-number is used as the build suffix. 19 - version: 1.0.0+14 19 + version: 1.0.0+24 20 20 21 21 environment: 22 22 sdk: ^3.8.1