refactor: Refactor null safety and improve code quality across multiple files

- Updated notifications_page.dart to handle potential null values for author avatar and display name.
- Enhanced profile_page.dart to ensure safe access to profile description and gallery items.
- Modified gallery_action_buttons.dart to utilize Riverpod for managing favorite state.
- Changed gallery_photo_view.dart to safely check for photo alt text.
- Improved gallery_preview.dart to filter photos based on non-null thumbnails.
- Updated justified_gallery_view.dart to handle aspect ratios safely and ensure thumbnails are not null.
- Refactored timeline_item.dart to use Riverpod for gallery data and ensure safe access to actor details.
- Updated pubspec.yaml and pubspec.lock to include new dependencies and overrides for analyzer and linting packages.

+4 -1
analysis_options.yaml
··· 23 rules: 24 # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 - 27 # Additional information about this file can be found at 28 # https://dart.dev/guides/language/analysis-options
··· 23 rules: 24 # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 # Additional information about this file can be found at 27 # https://dart.dev/guides/language/analysis-options 28 + 29 + analyzer: 30 + plugins: 31 + - custom_lint
+43 -1
lib/api.dart
··· 7 import 'package:grain/main.dart'; 8 import 'package:grain/models/atproto_session.dart'; 9 import 'package:http/http.dart' as http; 10 import 'package:mime/mime.dart'; 11 12 import './auth.dart'; ··· 39 throw Exception('Failed to fetch session'); 40 } 41 42 - return AtprotoSession.fromJson(jsonDecode(response.body)); 43 } 44 45 Future<Profile?> fetchCurrentUser() async { ··· 461 } 462 final result = jsonDecode(response.body) as Map<String, dynamic>; 463 appLogger.i('Created comment result: $result'); 464 return result['uri'] as String?; 465 } 466
··· 7 import 'package:grain/main.dart'; 8 import 'package:grain/models/atproto_session.dart'; 9 import 'package:http/http.dart' as http; 10 + import 'package:jose/jose.dart'; 11 import 'package:mime/mime.dart'; 12 13 import './auth.dart'; ··· 40 throw Exception('Failed to fetch session'); 41 } 42 43 + final json = jsonDecode(response.body); 44 + final token = json['tokenSet'] ?? {}; 45 + return AtprotoSession( 46 + accessToken: token['access_token'] as String, 47 + tokenType: token['token_type'] as String, 48 + expiresAt: DateTime.parse(token['expires_at'] as String), 49 + dpopJwk: JsonWebKey.fromJson(json['dpopJwk'] as Map<String, dynamic>), 50 + issuer: token['iss'] as String, 51 + subject: token['sub'] as String, 52 + ); 53 } 54 55 Future<Profile?> fetchCurrentUser() async { ··· 471 } 472 final result = jsonDecode(response.body) as Map<String, dynamic>; 473 appLogger.i('Created comment result: $result'); 474 + return result['uri'] as String?; 475 + } 476 + 477 + Future<String?> createFavorite({required String galleryUri}) async { 478 + final session = await auth.getValidSession(); 479 + if (session == null) { 480 + appLogger.w('No valid session for createFavorite'); 481 + return null; 482 + } 483 + final dpopClient = DpopHttpClient(dpopKey: session.dpopJwk); 484 + final issuer = session.issuer; 485 + final did = session.subject; 486 + final url = Uri.parse('$issuer/xrpc/com.atproto.repo.createRecord'); 487 + final record = { 488 + 'collection': 'social.grain.favorite', 489 + 'repo': did, 490 + 'record': {'subject': galleryUri, 'createdAt': DateTime.now().toUtc().toIso8601String()}, 491 + }; 492 + appLogger.i('Creating favorite: $record'); 493 + final response = await dpopClient.send( 494 + method: 'POST', 495 + url: url, 496 + accessToken: session.accessToken, 497 + headers: {'Content-Type': 'application/json'}, 498 + body: jsonEncode(record), 499 + ); 500 + if (response.statusCode != 200 && response.statusCode != 201) { 501 + appLogger.w('Failed to create favorite: \\${response.statusCode} \\${response.body}'); 502 + return null; 503 + } 504 + final result = jsonDecode(response.body) as Map<String, dynamic>; 505 + appLogger.i('Created favorite result: $result'); 506 return result['uri'] as String?; 507 } 508
+2 -1
lib/main.dart
··· 1 import 'package:flutter/foundation.dart'; 2 import 'package:flutter/material.dart'; 3 import 'package:flutter_dotenv/flutter_dotenv.dart'; 4 import 'package:grain/api.dart'; 5 import 'package:grain/app_logger.dart'; 6 import 'package:grain/app_theme.dart'; ··· 23 Future<void> main() async { 24 await AppConfig.init(); 25 appLogger.i('🚀 App started'); 26 - runApp(const MyApp()); 27 } 28 29 class MyApp extends StatefulWidget {
··· 1 import 'package:flutter/foundation.dart'; 2 import 'package:flutter/material.dart'; 3 import 'package:flutter_dotenv/flutter_dotenv.dart'; 4 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 import 'package:grain/api.dart'; 6 import 'package:grain/app_logger.dart'; 7 import 'package:grain/app_theme.dart'; ··· 24 Future<void> main() async { 25 await AppConfig.init(); 26 appLogger.i('🚀 App started'); 27 + runApp(const ProviderScope(child: MyApp())); 28 } 29 30 class MyApp extends StatefulWidget {
+11
lib/models/aspect_ratio.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'aspect_ratio.freezed.dart'; 4 + part 'aspect_ratio.g.dart'; 5 + 6 + @freezed 7 + class AspectRatio with _$AspectRatio { 8 + const factory AspectRatio({required int width, required int height}) = _AspectRatio; 9 + 10 + factory AspectRatio.fromJson(Map<String, dynamic> json) => _$AspectRatioFromJson(json); 11 + }
+184
lib/models/aspect_ratio.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 'aspect_ratio.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 + AspectRatio _$AspectRatioFromJson(Map<String, dynamic> json) { 19 + return _AspectRatio.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$AspectRatio { 24 + int get width => throw _privateConstructorUsedError; 25 + int get height => throw _privateConstructorUsedError; 26 + 27 + /// Serializes this AspectRatio to a JSON map. 28 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 29 + 30 + /// Create a copy of AspectRatio 31 + /// with the given fields replaced by the non-null parameter values. 32 + @JsonKey(includeFromJson: false, includeToJson: false) 33 + $AspectRatioCopyWith<AspectRatio> get copyWith => 34 + throw _privateConstructorUsedError; 35 + } 36 + 37 + /// @nodoc 38 + abstract class $AspectRatioCopyWith<$Res> { 39 + factory $AspectRatioCopyWith( 40 + AspectRatio value, 41 + $Res Function(AspectRatio) then, 42 + ) = _$AspectRatioCopyWithImpl<$Res, AspectRatio>; 43 + @useResult 44 + $Res call({int width, int height}); 45 + } 46 + 47 + /// @nodoc 48 + class _$AspectRatioCopyWithImpl<$Res, $Val extends AspectRatio> 49 + implements $AspectRatioCopyWith<$Res> { 50 + _$AspectRatioCopyWithImpl(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 AspectRatio 58 + /// with the given fields replaced by the non-null parameter values. 59 + @pragma('vm:prefer-inline') 60 + @override 61 + $Res call({Object? width = null, Object? height = null}) { 62 + return _then( 63 + _value.copyWith( 64 + width: null == width 65 + ? _value.width 66 + : width // ignore: cast_nullable_to_non_nullable 67 + as int, 68 + height: null == height 69 + ? _value.height 70 + : height // ignore: cast_nullable_to_non_nullable 71 + as int, 72 + ) 73 + as $Val, 74 + ); 75 + } 76 + } 77 + 78 + /// @nodoc 79 + abstract class _$$AspectRatioImplCopyWith<$Res> 80 + implements $AspectRatioCopyWith<$Res> { 81 + factory _$$AspectRatioImplCopyWith( 82 + _$AspectRatioImpl value, 83 + $Res Function(_$AspectRatioImpl) then, 84 + ) = __$$AspectRatioImplCopyWithImpl<$Res>; 85 + @override 86 + @useResult 87 + $Res call({int width, int height}); 88 + } 89 + 90 + /// @nodoc 91 + class __$$AspectRatioImplCopyWithImpl<$Res> 92 + extends _$AspectRatioCopyWithImpl<$Res, _$AspectRatioImpl> 93 + implements _$$AspectRatioImplCopyWith<$Res> { 94 + __$$AspectRatioImplCopyWithImpl( 95 + _$AspectRatioImpl _value, 96 + $Res Function(_$AspectRatioImpl) _then, 97 + ) : super(_value, _then); 98 + 99 + /// Create a copy of AspectRatio 100 + /// with the given fields replaced by the non-null parameter values. 101 + @pragma('vm:prefer-inline') 102 + @override 103 + $Res call({Object? width = null, Object? height = null}) { 104 + return _then( 105 + _$AspectRatioImpl( 106 + width: null == width 107 + ? _value.width 108 + : width // ignore: cast_nullable_to_non_nullable 109 + as int, 110 + height: null == height 111 + ? _value.height 112 + : height // ignore: cast_nullable_to_non_nullable 113 + as int, 114 + ), 115 + ); 116 + } 117 + } 118 + 119 + /// @nodoc 120 + @JsonSerializable() 121 + class _$AspectRatioImpl implements _AspectRatio { 122 + const _$AspectRatioImpl({required this.width, required this.height}); 123 + 124 + factory _$AspectRatioImpl.fromJson(Map<String, dynamic> json) => 125 + _$$AspectRatioImplFromJson(json); 126 + 127 + @override 128 + final int width; 129 + @override 130 + final int height; 131 + 132 + @override 133 + String toString() { 134 + return 'AspectRatio(width: $width, height: $height)'; 135 + } 136 + 137 + @override 138 + bool operator ==(Object other) { 139 + return identical(this, other) || 140 + (other.runtimeType == runtimeType && 141 + other is _$AspectRatioImpl && 142 + (identical(other.width, width) || other.width == width) && 143 + (identical(other.height, height) || other.height == height)); 144 + } 145 + 146 + @JsonKey(includeFromJson: false, includeToJson: false) 147 + @override 148 + int get hashCode => Object.hash(runtimeType, width, height); 149 + 150 + /// Create a copy of AspectRatio 151 + /// with the given fields replaced by the non-null parameter values. 152 + @JsonKey(includeFromJson: false, includeToJson: false) 153 + @override 154 + @pragma('vm:prefer-inline') 155 + _$$AspectRatioImplCopyWith<_$AspectRatioImpl> get copyWith => 156 + __$$AspectRatioImplCopyWithImpl<_$AspectRatioImpl>(this, _$identity); 157 + 158 + @override 159 + Map<String, dynamic> toJson() { 160 + return _$$AspectRatioImplToJson(this); 161 + } 162 + } 163 + 164 + abstract class _AspectRatio implements AspectRatio { 165 + const factory _AspectRatio({ 166 + required final int width, 167 + required final int height, 168 + }) = _$AspectRatioImpl; 169 + 170 + factory _AspectRatio.fromJson(Map<String, dynamic> json) = 171 + _$AspectRatioImpl.fromJson; 172 + 173 + @override 174 + int get width; 175 + @override 176 + int get height; 177 + 178 + /// Create a copy of AspectRatio 179 + /// with the given fields replaced by the non-null parameter values. 180 + @override 181 + @JsonKey(includeFromJson: false, includeToJson: false) 182 + _$$AspectRatioImplCopyWith<_$AspectRatioImpl> get copyWith => 183 + throw _privateConstructorUsedError; 184 + }
+16
lib/models/aspect_ratio.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'aspect_ratio.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$AspectRatioImpl _$$AspectRatioImplFromJson(Map<String, dynamic> json) => 10 + _$AspectRatioImpl( 11 + width: (json['width'] as num).toInt(), 12 + height: (json['height'] as num).toInt(), 13 + ); 14 + 15 + Map<String, dynamic> _$$AspectRatioImplToJson(_$AspectRatioImpl instance) => 16 + <String, dynamic>{'width': instance.width, 'height': instance.height};
+14 -43
lib/models/atproto_session.dart
··· 1 import 'package:jose/jose.dart'; 2 3 - class AtprotoSession { 4 - final String accessToken; 5 - final String? refreshToken; 6 - final String tokenType; 7 - final DateTime expiresAt; 8 - final JsonWebKey dpopJwk; 9 - final String issuer; 10 - final String subject; 11 12 - AtprotoSession({ 13 - required this.accessToken, 14 - this.refreshToken, 15 - required this.tokenType, 16 - required this.expiresAt, 17 - required this.dpopJwk, 18 - required this.issuer, 19 - required this.subject, 20 - }); 21 22 - factory AtprotoSession.fromJson(Map<String, dynamic> json) { 23 - final token = json['tokenSet'] ?? {}; 24 - final dpopJwkJson = json['dpopJwk'] ?? {}; 25 - 26 - return AtprotoSession( 27 - accessToken: token['access_token'], 28 - refreshToken: token['refresh_token'], 29 - tokenType: token['token_type'], 30 - expiresAt: DateTime.parse(token['expires_at']), 31 - dpopJwk: JsonWebKey.fromJson(dpopJwkJson), 32 - issuer: token['iss'], 33 - subject: token['sub'], 34 - ); 35 - } 36 - 37 - Map<String, dynamic> toJson() => { 38 - 'tokenSet': { 39 - 'access_token': accessToken, 40 - 'refresh_token': refreshToken, 41 - 'token_type': tokenType, 42 - 'expires_at': expiresAt.toIso8601String(), 43 - 'iss': issuer, 44 - 'sub': subject, 45 - }, 46 - 'dpopJwk': dpopJwk.toJson(), 47 - }; 48 }
··· 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 + };
+18 -35
lib/models/comment.dart
··· 1 - import 'gallery.dart'; 2 3 - class Comment { 4 - final String uri; 5 - final String cid; 6 - final Map<String, dynamic> author; 7 - final String text; 8 - final String? replyTo; 9 - final String? createdAt; 10 - final GalleryPhoto? focus; 11 - final List<Map<String, dynamic>>? facets; 12 13 - Comment({ 14 - required this.uri, 15 - required this.cid, 16 - required this.author, 17 - required this.text, 18 - this.replyTo, 19 - this.createdAt, 20 - this.focus, 21 - this.facets, 22 - }); 23 24 - factory Comment.fromJson(Map<String, dynamic> json) { 25 - final record = json['record'] as Map<String, dynamic>? ?? {}; 26 - return Comment( 27 - uri: json['uri'] ?? '', 28 - cid: json['cid'] ?? '', 29 - author: json['author'] ?? {}, 30 - text: json['text'] ?? record['text'] ?? '', 31 - replyTo: json['replyTo'] ?? record['replyTo'], 32 - createdAt: json['createdAt'] ?? record['createdAt'], 33 - focus: json['focus'] != null ? GalleryPhoto.fromJson(json['focus']) : null, 34 - facets: 35 - (json['facets'] as List?)?.map((f) => Map<String, dynamic>.from(f)).toList() ?? 36 - (record['facets'] as List?)?.map((f) => Map<String, dynamic>.from(f)).toList(), 37 - ); 38 - } 39 }
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 3 + import 'gallery_photo.dart'; 4 + 5 + part 'comment.freezed.dart'; 6 + part 'comment.g.dart'; 7 8 + @freezed 9 + class Comment with _$Comment { 10 + const factory Comment({ 11 + required String uri, 12 + required String cid, 13 + required Map<String, dynamic> author, 14 + required String text, 15 + String? replyTo, 16 + String? createdAt, 17 + GalleryPhoto? focus, 18 + List<Map<String, dynamic>>? facets, 19 + }) = _Comment; 20 21 + factory Comment.fromJson(Map<String, dynamic> json) => _$CommentFromJson(json); 22 }
+358
lib/models/comment.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 'comment.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 + Comment _$CommentFromJson(Map<String, dynamic> json) { 19 + return _Comment.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$Comment { 24 + String get uri => throw _privateConstructorUsedError; 25 + String get cid => throw _privateConstructorUsedError; 26 + Map<String, dynamic> get author => throw _privateConstructorUsedError; 27 + String get text => throw _privateConstructorUsedError; 28 + String? get replyTo => throw _privateConstructorUsedError; 29 + String? get createdAt => throw _privateConstructorUsedError; 30 + GalleryPhoto? get focus => throw _privateConstructorUsedError; 31 + List<Map<String, dynamic>>? get facets => throw _privateConstructorUsedError; 32 + 33 + /// Serializes this Comment to a JSON map. 34 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 35 + 36 + /// Create a copy of Comment 37 + /// with the given fields replaced by the non-null parameter values. 38 + @JsonKey(includeFromJson: false, includeToJson: false) 39 + $CommentCopyWith<Comment> get copyWith => throw _privateConstructorUsedError; 40 + } 41 + 42 + /// @nodoc 43 + abstract class $CommentCopyWith<$Res> { 44 + factory $CommentCopyWith(Comment value, $Res Function(Comment) then) = 45 + _$CommentCopyWithImpl<$Res, Comment>; 46 + @useResult 47 + $Res call({ 48 + String uri, 49 + String cid, 50 + Map<String, dynamic> author, 51 + String text, 52 + String? replyTo, 53 + String? createdAt, 54 + GalleryPhoto? focus, 55 + List<Map<String, dynamic>>? facets, 56 + }); 57 + 58 + $GalleryPhotoCopyWith<$Res>? get focus; 59 + } 60 + 61 + /// @nodoc 62 + class _$CommentCopyWithImpl<$Res, $Val extends Comment> 63 + implements $CommentCopyWith<$Res> { 64 + _$CommentCopyWithImpl(this._value, this._then); 65 + 66 + // ignore: unused_field 67 + final $Val _value; 68 + // ignore: unused_field 69 + final $Res Function($Val) _then; 70 + 71 + /// Create a copy of Comment 72 + /// with the given fields replaced by the non-null parameter values. 73 + @pragma('vm:prefer-inline') 74 + @override 75 + $Res call({ 76 + Object? uri = null, 77 + Object? cid = null, 78 + Object? author = null, 79 + Object? text = null, 80 + Object? replyTo = freezed, 81 + Object? createdAt = freezed, 82 + Object? focus = freezed, 83 + Object? facets = freezed, 84 + }) { 85 + return _then( 86 + _value.copyWith( 87 + uri: null == uri 88 + ? _value.uri 89 + : uri // ignore: cast_nullable_to_non_nullable 90 + as String, 91 + cid: null == cid 92 + ? _value.cid 93 + : cid // ignore: cast_nullable_to_non_nullable 94 + as String, 95 + author: null == author 96 + ? _value.author 97 + : author // ignore: cast_nullable_to_non_nullable 98 + as Map<String, dynamic>, 99 + text: null == text 100 + ? _value.text 101 + : text // ignore: cast_nullable_to_non_nullable 102 + as String, 103 + replyTo: freezed == replyTo 104 + ? _value.replyTo 105 + : replyTo // ignore: cast_nullable_to_non_nullable 106 + as String?, 107 + createdAt: freezed == createdAt 108 + ? _value.createdAt 109 + : createdAt // ignore: cast_nullable_to_non_nullable 110 + as String?, 111 + focus: freezed == focus 112 + ? _value.focus 113 + : focus // ignore: cast_nullable_to_non_nullable 114 + as GalleryPhoto?, 115 + facets: freezed == facets 116 + ? _value.facets 117 + : facets // ignore: cast_nullable_to_non_nullable 118 + as List<Map<String, dynamic>>?, 119 + ) 120 + as $Val, 121 + ); 122 + } 123 + 124 + /// Create a copy of Comment 125 + /// with the given fields replaced by the non-null parameter values. 126 + @override 127 + @pragma('vm:prefer-inline') 128 + $GalleryPhotoCopyWith<$Res>? get focus { 129 + if (_value.focus == null) { 130 + return null; 131 + } 132 + 133 + return $GalleryPhotoCopyWith<$Res>(_value.focus!, (value) { 134 + return _then(_value.copyWith(focus: value) as $Val); 135 + }); 136 + } 137 + } 138 + 139 + /// @nodoc 140 + abstract class _$$CommentImplCopyWith<$Res> implements $CommentCopyWith<$Res> { 141 + factory _$$CommentImplCopyWith( 142 + _$CommentImpl value, 143 + $Res Function(_$CommentImpl) then, 144 + ) = __$$CommentImplCopyWithImpl<$Res>; 145 + @override 146 + @useResult 147 + $Res call({ 148 + String uri, 149 + String cid, 150 + Map<String, dynamic> author, 151 + String text, 152 + String? replyTo, 153 + String? createdAt, 154 + GalleryPhoto? focus, 155 + List<Map<String, dynamic>>? facets, 156 + }); 157 + 158 + @override 159 + $GalleryPhotoCopyWith<$Res>? get focus; 160 + } 161 + 162 + /// @nodoc 163 + class __$$CommentImplCopyWithImpl<$Res> 164 + extends _$CommentCopyWithImpl<$Res, _$CommentImpl> 165 + implements _$$CommentImplCopyWith<$Res> { 166 + __$$CommentImplCopyWithImpl( 167 + _$CommentImpl _value, 168 + $Res Function(_$CommentImpl) _then, 169 + ) : super(_value, _then); 170 + 171 + /// Create a copy of Comment 172 + /// with the given fields replaced by the non-null parameter values. 173 + @pragma('vm:prefer-inline') 174 + @override 175 + $Res call({ 176 + Object? uri = null, 177 + Object? cid = null, 178 + Object? author = null, 179 + Object? text = null, 180 + Object? replyTo = freezed, 181 + Object? createdAt = freezed, 182 + Object? focus = freezed, 183 + Object? facets = freezed, 184 + }) { 185 + return _then( 186 + _$CommentImpl( 187 + uri: null == uri 188 + ? _value.uri 189 + : uri // ignore: cast_nullable_to_non_nullable 190 + as String, 191 + cid: null == cid 192 + ? _value.cid 193 + : cid // ignore: cast_nullable_to_non_nullable 194 + as String, 195 + author: null == author 196 + ? _value._author 197 + : author // ignore: cast_nullable_to_non_nullable 198 + as Map<String, dynamic>, 199 + text: null == text 200 + ? _value.text 201 + : text // ignore: cast_nullable_to_non_nullable 202 + as String, 203 + replyTo: freezed == replyTo 204 + ? _value.replyTo 205 + : replyTo // ignore: cast_nullable_to_non_nullable 206 + as String?, 207 + createdAt: freezed == createdAt 208 + ? _value.createdAt 209 + : createdAt // ignore: cast_nullable_to_non_nullable 210 + as String?, 211 + focus: freezed == focus 212 + ? _value.focus 213 + : focus // ignore: cast_nullable_to_non_nullable 214 + as GalleryPhoto?, 215 + facets: freezed == facets 216 + ? _value._facets 217 + : facets // ignore: cast_nullable_to_non_nullable 218 + as List<Map<String, dynamic>>?, 219 + ), 220 + ); 221 + } 222 + } 223 + 224 + /// @nodoc 225 + @JsonSerializable() 226 + class _$CommentImpl implements _Comment { 227 + const _$CommentImpl({ 228 + required this.uri, 229 + required this.cid, 230 + required final Map<String, dynamic> author, 231 + required this.text, 232 + this.replyTo, 233 + this.createdAt, 234 + this.focus, 235 + final List<Map<String, dynamic>>? facets, 236 + }) : _author = author, 237 + _facets = facets; 238 + 239 + factory _$CommentImpl.fromJson(Map<String, dynamic> json) => 240 + _$$CommentImplFromJson(json); 241 + 242 + @override 243 + final String uri; 244 + @override 245 + final String cid; 246 + final Map<String, dynamic> _author; 247 + @override 248 + Map<String, dynamic> get author { 249 + if (_author is EqualUnmodifiableMapView) return _author; 250 + // ignore: implicit_dynamic_type 251 + return EqualUnmodifiableMapView(_author); 252 + } 253 + 254 + @override 255 + final String text; 256 + @override 257 + final String? replyTo; 258 + @override 259 + final String? createdAt; 260 + @override 261 + final GalleryPhoto? focus; 262 + final List<Map<String, dynamic>>? _facets; 263 + @override 264 + List<Map<String, dynamic>>? get facets { 265 + final value = _facets; 266 + if (value == null) return null; 267 + if (_facets is EqualUnmodifiableListView) return _facets; 268 + // ignore: implicit_dynamic_type 269 + return EqualUnmodifiableListView(value); 270 + } 271 + 272 + @override 273 + String toString() { 274 + return 'Comment(uri: $uri, cid: $cid, author: $author, text: $text, replyTo: $replyTo, createdAt: $createdAt, focus: $focus, facets: $facets)'; 275 + } 276 + 277 + @override 278 + bool operator ==(Object other) { 279 + return identical(this, other) || 280 + (other.runtimeType == runtimeType && 281 + other is _$CommentImpl && 282 + (identical(other.uri, uri) || other.uri == uri) && 283 + (identical(other.cid, cid) || other.cid == cid) && 284 + const DeepCollectionEquality().equals(other._author, _author) && 285 + (identical(other.text, text) || other.text == text) && 286 + (identical(other.replyTo, replyTo) || other.replyTo == replyTo) && 287 + (identical(other.createdAt, createdAt) || 288 + other.createdAt == createdAt) && 289 + (identical(other.focus, focus) || other.focus == focus) && 290 + const DeepCollectionEquality().equals(other._facets, _facets)); 291 + } 292 + 293 + @JsonKey(includeFromJson: false, includeToJson: false) 294 + @override 295 + int get hashCode => Object.hash( 296 + runtimeType, 297 + uri, 298 + cid, 299 + const DeepCollectionEquality().hash(_author), 300 + text, 301 + replyTo, 302 + createdAt, 303 + focus, 304 + const DeepCollectionEquality().hash(_facets), 305 + ); 306 + 307 + /// Create a copy of Comment 308 + /// with the given fields replaced by the non-null parameter values. 309 + @JsonKey(includeFromJson: false, includeToJson: false) 310 + @override 311 + @pragma('vm:prefer-inline') 312 + _$$CommentImplCopyWith<_$CommentImpl> get copyWith => 313 + __$$CommentImplCopyWithImpl<_$CommentImpl>(this, _$identity); 314 + 315 + @override 316 + Map<String, dynamic> toJson() { 317 + return _$$CommentImplToJson(this); 318 + } 319 + } 320 + 321 + abstract class _Comment implements Comment { 322 + const factory _Comment({ 323 + required final String uri, 324 + required final String cid, 325 + required final Map<String, dynamic> author, 326 + required final String text, 327 + final String? replyTo, 328 + final String? createdAt, 329 + final GalleryPhoto? focus, 330 + final List<Map<String, dynamic>>? facets, 331 + }) = _$CommentImpl; 332 + 333 + factory _Comment.fromJson(Map<String, dynamic> json) = _$CommentImpl.fromJson; 334 + 335 + @override 336 + String get uri; 337 + @override 338 + String get cid; 339 + @override 340 + Map<String, dynamic> get author; 341 + @override 342 + String get text; 343 + @override 344 + String? get replyTo; 345 + @override 346 + String? get createdAt; 347 + @override 348 + GalleryPhoto? get focus; 349 + @override 350 + List<Map<String, dynamic>>? get facets; 351 + 352 + /// Create a copy of Comment 353 + /// with the given fields replaced by the non-null parameter values. 354 + @override 355 + @JsonKey(includeFromJson: false, includeToJson: false) 356 + _$$CommentImplCopyWith<_$CommentImpl> get copyWith => 357 + throw _privateConstructorUsedError; 358 + }
+35
lib/models/comment.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'comment.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$CommentImpl _$$CommentImplFromJson(Map<String, dynamic> json) => 10 + _$CommentImpl( 11 + uri: json['uri'] as String, 12 + cid: json['cid'] as String, 13 + author: json['author'] as Map<String, dynamic>, 14 + text: json['text'] as String, 15 + replyTo: json['replyTo'] as String?, 16 + createdAt: json['createdAt'] as String?, 17 + focus: json['focus'] == null 18 + ? null 19 + : GalleryPhoto.fromJson(json['focus'] as Map<String, dynamic>), 20 + facets: (json['facets'] as List<dynamic>?) 21 + ?.map((e) => e as Map<String, dynamic>) 22 + .toList(), 23 + ); 24 + 25 + Map<String, dynamic> _$$CommentImplToJson(_$CommentImpl instance) => 26 + <String, dynamic>{ 27 + 'uri': instance.uri, 28 + 'cid': instance.cid, 29 + 'author': instance.author, 30 + 'text': instance.text, 31 + 'replyTo': instance.replyTo, 32 + 'createdAt': instance.createdAt, 33 + 'focus': instance.focus, 34 + 'facets': instance.facets, 35 + };
+22 -75
lib/models/gallery.dart
··· 1 import 'profile.dart'; 2 3 - class Gallery { 4 - final String uri; 5 - final String cid; 6 - final String title; 7 - final String description; 8 - final List<GalleryPhoto> items; 9 - final Profile? creator; 10 - final String? createdAt; 11 - final int? favCount; 12 - final int? commentCount; 13 - final Map<String, dynamic>? viewer; 14 - final List<Map<String, dynamic>>? facets; 15 16 - Gallery({ 17 - required this.uri, 18 - required this.cid, 19 - required this.title, 20 - required this.description, 21 - required this.items, 22 - this.creator, 23 - this.createdAt, 24 - this.favCount, 25 - this.commentCount, 26 - this.viewer, 27 - this.facets, 28 - }); 29 30 - factory Gallery.fromJson(Map<String, dynamic> json) { 31 - final record = json['record'] as Map<String, dynamic>? ?? {}; 32 - return Gallery( 33 - uri: json['uri'] ?? '', 34 - cid: json['cid'] ?? '', 35 - title: record['title'] ?? '', 36 - description: record['description'] ?? '', 37 - items: (json['items'] as List<dynamic>? ?? []) 38 - .map((item) => GalleryPhoto.fromJson(item as Map<String, dynamic>)) 39 - .toList(), 40 - creator: json['creator'] != null ? Profile.fromJson(json['creator']) : null, 41 - createdAt: json['createdAt'], 42 - favCount: json['favCount'], 43 - commentCount: json['commentCount'], 44 - viewer: json['viewer'], 45 - facets: (record['facets'] as List?)?.map((f) => Map<String, dynamic>.from(f)).toList(), 46 - ); 47 - } 48 - } 49 - 50 - class GalleryPhoto { 51 - final String uri; 52 - final String cid; 53 - final String thumb; 54 - final String fullsize; 55 - final String alt; 56 - final int width; 57 - final int height; 58 - 59 - GalleryPhoto({ 60 - required this.uri, 61 - required this.cid, 62 - required this.thumb, 63 - required this.fullsize, 64 - required this.alt, 65 - required this.width, 66 - required this.height, 67 - }); 68 - 69 - factory GalleryPhoto.fromJson(Map<String, dynamic> json) { 70 - return GalleryPhoto( 71 - uri: json['uri'] ?? '', 72 - cid: json['cid'] ?? '', 73 - thumb: json['thumb'] ?? '', 74 - fullsize: json['fullsize'] ?? '', 75 - alt: json['alt'] ?? '', 76 - width: json['aspectRatio']?['width'] ?? 0, 77 - height: json['aspectRatio']?['height'] ?? 0, 78 - ); 79 - } 80 }
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + import 'gallery_photo.dart'; 4 + import 'gallery_viewer.dart'; 5 import 'profile.dart'; 6 7 + part 'gallery.freezed.dart'; 8 + part 'gallery.g.dart'; 9 10 + @freezed 11 + class Gallery with _$Gallery { 12 + const factory Gallery({ 13 + required String uri, 14 + required String cid, 15 + String? title, 16 + String? description, 17 + required List<GalleryPhoto> items, 18 + Profile? creator, 19 + String? createdAt, 20 + int? favCount, 21 + int? commentCount, 22 + GalleryViewer? viewer, 23 + List<Map<String, dynamic>>? facets, 24 + }) = _Gallery; 25 26 + factory Gallery.fromJson(Map<String, dynamic> json) => _$GalleryFromJson(json); 27 }
+441
lib/models/gallery.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 'gallery.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 + Gallery _$GalleryFromJson(Map<String, dynamic> json) { 19 + return _Gallery.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$Gallery { 24 + String get uri => throw _privateConstructorUsedError; 25 + String get cid => throw _privateConstructorUsedError; 26 + String? get title => throw _privateConstructorUsedError; 27 + String? get description => throw _privateConstructorUsedError; 28 + List<GalleryPhoto> get items => throw _privateConstructorUsedError; 29 + Profile? get creator => throw _privateConstructorUsedError; 30 + String? get createdAt => throw _privateConstructorUsedError; 31 + int? get favCount => throw _privateConstructorUsedError; 32 + int? get commentCount => throw _privateConstructorUsedError; 33 + GalleryViewer? get viewer => throw _privateConstructorUsedError; 34 + List<Map<String, dynamic>>? get facets => throw _privateConstructorUsedError; 35 + 36 + /// Serializes this Gallery to a JSON map. 37 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 38 + 39 + /// Create a copy of Gallery 40 + /// with the given fields replaced by the non-null parameter values. 41 + @JsonKey(includeFromJson: false, includeToJson: false) 42 + $GalleryCopyWith<Gallery> get copyWith => throw _privateConstructorUsedError; 43 + } 44 + 45 + /// @nodoc 46 + abstract class $GalleryCopyWith<$Res> { 47 + factory $GalleryCopyWith(Gallery value, $Res Function(Gallery) then) = 48 + _$GalleryCopyWithImpl<$Res, Gallery>; 49 + @useResult 50 + $Res call({ 51 + String uri, 52 + String cid, 53 + String? title, 54 + String? description, 55 + List<GalleryPhoto> items, 56 + Profile? creator, 57 + String? createdAt, 58 + int? favCount, 59 + int? commentCount, 60 + GalleryViewer? viewer, 61 + List<Map<String, dynamic>>? facets, 62 + }); 63 + 64 + $ProfileCopyWith<$Res>? get creator; 65 + $GalleryViewerCopyWith<$Res>? get viewer; 66 + } 67 + 68 + /// @nodoc 69 + class _$GalleryCopyWithImpl<$Res, $Val extends Gallery> 70 + implements $GalleryCopyWith<$Res> { 71 + _$GalleryCopyWithImpl(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 Gallery 79 + /// with the given fields replaced by the non-null parameter values. 80 + @pragma('vm:prefer-inline') 81 + @override 82 + $Res call({ 83 + Object? uri = null, 84 + Object? cid = null, 85 + Object? title = freezed, 86 + Object? description = freezed, 87 + Object? items = null, 88 + Object? creator = freezed, 89 + Object? createdAt = freezed, 90 + Object? favCount = freezed, 91 + Object? commentCount = freezed, 92 + Object? viewer = freezed, 93 + Object? facets = freezed, 94 + }) { 95 + return _then( 96 + _value.copyWith( 97 + uri: null == uri 98 + ? _value.uri 99 + : uri // ignore: cast_nullable_to_non_nullable 100 + as String, 101 + cid: null == cid 102 + ? _value.cid 103 + : cid // ignore: cast_nullable_to_non_nullable 104 + as String, 105 + title: freezed == title 106 + ? _value.title 107 + : title // ignore: cast_nullable_to_non_nullable 108 + as String?, 109 + description: freezed == description 110 + ? _value.description 111 + : description // ignore: cast_nullable_to_non_nullable 112 + as String?, 113 + items: null == items 114 + ? _value.items 115 + : items // ignore: cast_nullable_to_non_nullable 116 + as List<GalleryPhoto>, 117 + creator: freezed == creator 118 + ? _value.creator 119 + : creator // ignore: cast_nullable_to_non_nullable 120 + as Profile?, 121 + createdAt: freezed == createdAt 122 + ? _value.createdAt 123 + : createdAt // ignore: cast_nullable_to_non_nullable 124 + as String?, 125 + favCount: freezed == favCount 126 + ? _value.favCount 127 + : favCount // ignore: cast_nullable_to_non_nullable 128 + as int?, 129 + commentCount: freezed == commentCount 130 + ? _value.commentCount 131 + : commentCount // ignore: cast_nullable_to_non_nullable 132 + as int?, 133 + viewer: freezed == viewer 134 + ? _value.viewer 135 + : viewer // ignore: cast_nullable_to_non_nullable 136 + as GalleryViewer?, 137 + facets: freezed == facets 138 + ? _value.facets 139 + : facets // ignore: cast_nullable_to_non_nullable 140 + as List<Map<String, dynamic>>?, 141 + ) 142 + as $Val, 143 + ); 144 + } 145 + 146 + /// Create a copy of Gallery 147 + /// with the given fields replaced by the non-null parameter values. 148 + @override 149 + @pragma('vm:prefer-inline') 150 + $ProfileCopyWith<$Res>? get creator { 151 + if (_value.creator == null) { 152 + return null; 153 + } 154 + 155 + return $ProfileCopyWith<$Res>(_value.creator!, (value) { 156 + return _then(_value.copyWith(creator: value) as $Val); 157 + }); 158 + } 159 + 160 + /// Create a copy of Gallery 161 + /// with the given fields replaced by the non-null parameter values. 162 + @override 163 + @pragma('vm:prefer-inline') 164 + $GalleryViewerCopyWith<$Res>? get viewer { 165 + if (_value.viewer == null) { 166 + return null; 167 + } 168 + 169 + return $GalleryViewerCopyWith<$Res>(_value.viewer!, (value) { 170 + return _then(_value.copyWith(viewer: value) as $Val); 171 + }); 172 + } 173 + } 174 + 175 + /// @nodoc 176 + abstract class _$$GalleryImplCopyWith<$Res> implements $GalleryCopyWith<$Res> { 177 + factory _$$GalleryImplCopyWith( 178 + _$GalleryImpl value, 179 + $Res Function(_$GalleryImpl) then, 180 + ) = __$$GalleryImplCopyWithImpl<$Res>; 181 + @override 182 + @useResult 183 + $Res call({ 184 + String uri, 185 + String cid, 186 + String? title, 187 + String? description, 188 + List<GalleryPhoto> items, 189 + Profile? creator, 190 + String? createdAt, 191 + int? favCount, 192 + int? commentCount, 193 + GalleryViewer? viewer, 194 + List<Map<String, dynamic>>? facets, 195 + }); 196 + 197 + @override 198 + $ProfileCopyWith<$Res>? get creator; 199 + @override 200 + $GalleryViewerCopyWith<$Res>? get viewer; 201 + } 202 + 203 + /// @nodoc 204 + class __$$GalleryImplCopyWithImpl<$Res> 205 + extends _$GalleryCopyWithImpl<$Res, _$GalleryImpl> 206 + implements _$$GalleryImplCopyWith<$Res> { 207 + __$$GalleryImplCopyWithImpl( 208 + _$GalleryImpl _value, 209 + $Res Function(_$GalleryImpl) _then, 210 + ) : super(_value, _then); 211 + 212 + /// Create a copy of Gallery 213 + /// with the given fields replaced by the non-null parameter values. 214 + @pragma('vm:prefer-inline') 215 + @override 216 + $Res call({ 217 + Object? uri = null, 218 + Object? cid = null, 219 + Object? title = freezed, 220 + Object? description = freezed, 221 + Object? items = null, 222 + Object? creator = freezed, 223 + Object? createdAt = freezed, 224 + Object? favCount = freezed, 225 + Object? commentCount = freezed, 226 + Object? viewer = freezed, 227 + Object? facets = freezed, 228 + }) { 229 + return _then( 230 + _$GalleryImpl( 231 + uri: null == uri 232 + ? _value.uri 233 + : uri // ignore: cast_nullable_to_non_nullable 234 + as String, 235 + cid: null == cid 236 + ? _value.cid 237 + : cid // ignore: cast_nullable_to_non_nullable 238 + as String, 239 + title: freezed == title 240 + ? _value.title 241 + : title // ignore: cast_nullable_to_non_nullable 242 + as String?, 243 + description: freezed == description 244 + ? _value.description 245 + : description // ignore: cast_nullable_to_non_nullable 246 + as String?, 247 + items: null == items 248 + ? _value._items 249 + : items // ignore: cast_nullable_to_non_nullable 250 + as List<GalleryPhoto>, 251 + creator: freezed == creator 252 + ? _value.creator 253 + : creator // ignore: cast_nullable_to_non_nullable 254 + as Profile?, 255 + createdAt: freezed == createdAt 256 + ? _value.createdAt 257 + : createdAt // ignore: cast_nullable_to_non_nullable 258 + as String?, 259 + favCount: freezed == favCount 260 + ? _value.favCount 261 + : favCount // ignore: cast_nullable_to_non_nullable 262 + as int?, 263 + commentCount: freezed == commentCount 264 + ? _value.commentCount 265 + : commentCount // ignore: cast_nullable_to_non_nullable 266 + as int?, 267 + viewer: freezed == viewer 268 + ? _value.viewer 269 + : viewer // ignore: cast_nullable_to_non_nullable 270 + as GalleryViewer?, 271 + facets: freezed == facets 272 + ? _value._facets 273 + : facets // ignore: cast_nullable_to_non_nullable 274 + as List<Map<String, dynamic>>?, 275 + ), 276 + ); 277 + } 278 + } 279 + 280 + /// @nodoc 281 + @JsonSerializable() 282 + class _$GalleryImpl implements _Gallery { 283 + const _$GalleryImpl({ 284 + required this.uri, 285 + required this.cid, 286 + this.title, 287 + this.description, 288 + required final List<GalleryPhoto> items, 289 + this.creator, 290 + this.createdAt, 291 + this.favCount, 292 + this.commentCount, 293 + this.viewer, 294 + final List<Map<String, dynamic>>? facets, 295 + }) : _items = items, 296 + _facets = facets; 297 + 298 + factory _$GalleryImpl.fromJson(Map<String, dynamic> json) => 299 + _$$GalleryImplFromJson(json); 300 + 301 + @override 302 + final String uri; 303 + @override 304 + final String cid; 305 + @override 306 + final String? title; 307 + @override 308 + final String? description; 309 + final List<GalleryPhoto> _items; 310 + @override 311 + List<GalleryPhoto> get items { 312 + if (_items is EqualUnmodifiableListView) return _items; 313 + // ignore: implicit_dynamic_type 314 + return EqualUnmodifiableListView(_items); 315 + } 316 + 317 + @override 318 + final Profile? creator; 319 + @override 320 + final String? createdAt; 321 + @override 322 + final int? favCount; 323 + @override 324 + final int? commentCount; 325 + @override 326 + final GalleryViewer? viewer; 327 + final List<Map<String, dynamic>>? _facets; 328 + @override 329 + List<Map<String, dynamic>>? get facets { 330 + final value = _facets; 331 + if (value == null) return null; 332 + if (_facets is EqualUnmodifiableListView) return _facets; 333 + // ignore: implicit_dynamic_type 334 + return EqualUnmodifiableListView(value); 335 + } 336 + 337 + @override 338 + String toString() { 339 + return 'Gallery(uri: $uri, cid: $cid, title: $title, description: $description, items: $items, creator: $creator, createdAt: $createdAt, favCount: $favCount, commentCount: $commentCount, viewer: $viewer, facets: $facets)'; 340 + } 341 + 342 + @override 343 + bool operator ==(Object other) { 344 + return identical(this, other) || 345 + (other.runtimeType == runtimeType && 346 + other is _$GalleryImpl && 347 + (identical(other.uri, uri) || other.uri == uri) && 348 + (identical(other.cid, cid) || other.cid == cid) && 349 + (identical(other.title, title) || other.title == title) && 350 + (identical(other.description, description) || 351 + other.description == description) && 352 + const DeepCollectionEquality().equals(other._items, _items) && 353 + (identical(other.creator, creator) || other.creator == creator) && 354 + (identical(other.createdAt, createdAt) || 355 + other.createdAt == createdAt) && 356 + (identical(other.favCount, favCount) || 357 + other.favCount == favCount) && 358 + (identical(other.commentCount, commentCount) || 359 + other.commentCount == commentCount) && 360 + (identical(other.viewer, viewer) || other.viewer == viewer) && 361 + const DeepCollectionEquality().equals(other._facets, _facets)); 362 + } 363 + 364 + @JsonKey(includeFromJson: false, includeToJson: false) 365 + @override 366 + int get hashCode => Object.hash( 367 + runtimeType, 368 + uri, 369 + cid, 370 + title, 371 + description, 372 + const DeepCollectionEquality().hash(_items), 373 + creator, 374 + createdAt, 375 + favCount, 376 + commentCount, 377 + viewer, 378 + const DeepCollectionEquality().hash(_facets), 379 + ); 380 + 381 + /// Create a copy of Gallery 382 + /// with the given fields replaced by the non-null parameter values. 383 + @JsonKey(includeFromJson: false, includeToJson: false) 384 + @override 385 + @pragma('vm:prefer-inline') 386 + _$$GalleryImplCopyWith<_$GalleryImpl> get copyWith => 387 + __$$GalleryImplCopyWithImpl<_$GalleryImpl>(this, _$identity); 388 + 389 + @override 390 + Map<String, dynamic> toJson() { 391 + return _$$GalleryImplToJson(this); 392 + } 393 + } 394 + 395 + abstract class _Gallery implements Gallery { 396 + const factory _Gallery({ 397 + required final String uri, 398 + required final String cid, 399 + final String? title, 400 + final String? description, 401 + required final List<GalleryPhoto> items, 402 + final Profile? creator, 403 + final String? createdAt, 404 + final int? favCount, 405 + final int? commentCount, 406 + final GalleryViewer? viewer, 407 + final List<Map<String, dynamic>>? facets, 408 + }) = _$GalleryImpl; 409 + 410 + factory _Gallery.fromJson(Map<String, dynamic> json) = _$GalleryImpl.fromJson; 411 + 412 + @override 413 + String get uri; 414 + @override 415 + String get cid; 416 + @override 417 + String? get title; 418 + @override 419 + String? get description; 420 + @override 421 + List<GalleryPhoto> get items; 422 + @override 423 + Profile? get creator; 424 + @override 425 + String? get createdAt; 426 + @override 427 + int? get favCount; 428 + @override 429 + int? get commentCount; 430 + @override 431 + GalleryViewer? get viewer; 432 + @override 433 + List<Map<String, dynamic>>? get facets; 434 + 435 + /// Create a copy of Gallery 436 + /// with the given fields replaced by the non-null parameter values. 437 + @override 438 + @JsonKey(includeFromJson: false, includeToJson: false) 439 + _$$GalleryImplCopyWith<_$GalleryImpl> get copyWith => 440 + throw _privateConstructorUsedError; 441 + }
+45
lib/models/gallery.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'gallery.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$GalleryImpl _$$GalleryImplFromJson(Map<String, dynamic> json) => 10 + _$GalleryImpl( 11 + uri: json['uri'] as String, 12 + cid: json['cid'] as String, 13 + title: json['title'] as String?, 14 + description: json['description'] as String?, 15 + items: (json['items'] as List<dynamic>) 16 + .map((e) => GalleryPhoto.fromJson(e as Map<String, dynamic>)) 17 + .toList(), 18 + creator: json['creator'] == null 19 + ? null 20 + : Profile.fromJson(json['creator'] as Map<String, dynamic>), 21 + createdAt: json['createdAt'] as String?, 22 + favCount: (json['favCount'] as num?)?.toInt(), 23 + commentCount: (json['commentCount'] as num?)?.toInt(), 24 + viewer: json['viewer'] == null 25 + ? null 26 + : GalleryViewer.fromJson(json['viewer'] as Map<String, dynamic>), 27 + facets: (json['facets'] as List<dynamic>?) 28 + ?.map((e) => e as Map<String, dynamic>) 29 + .toList(), 30 + ); 31 + 32 + Map<String, dynamic> _$$GalleryImplToJson(_$GalleryImpl instance) => 33 + <String, dynamic>{ 34 + 'uri': instance.uri, 35 + 'cid': instance.cid, 36 + 'title': instance.title, 37 + 'description': instance.description, 38 + 'items': instance.items, 39 + 'creator': instance.creator, 40 + 'createdAt': instance.createdAt, 41 + 'favCount': instance.favCount, 42 + 'commentCount': instance.commentCount, 43 + 'viewer': instance.viewer, 44 + 'facets': instance.facets, 45 + };
+20
lib/models/gallery_photo.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + import 'aspect_ratio.dart'; 4 + 5 + part 'gallery_photo.freezed.dart'; 6 + part 'gallery_photo.g.dart'; 7 + 8 + @freezed 9 + class GalleryPhoto with _$GalleryPhoto { 10 + const factory GalleryPhoto({ 11 + String? uri, 12 + String? cid, 13 + String? thumb, 14 + String? fullsize, 15 + String? alt, 16 + AspectRatio? aspectRatio, 17 + }) = _GalleryPhoto; 18 + 19 + factory GalleryPhoto.fromJson(Map<String, dynamic> json) => _$GalleryPhotoFromJson(json); 20 + }
+301
lib/models/gallery_photo.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 'gallery_photo.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 + GalleryPhoto _$GalleryPhotoFromJson(Map<String, dynamic> json) { 19 + return _GalleryPhoto.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$GalleryPhoto { 24 + String? get uri => throw _privateConstructorUsedError; 25 + String? get cid => throw _privateConstructorUsedError; 26 + String? get thumb => throw _privateConstructorUsedError; 27 + String? get fullsize => throw _privateConstructorUsedError; 28 + String? get alt => throw _privateConstructorUsedError; 29 + AspectRatio? get aspectRatio => throw _privateConstructorUsedError; 30 + 31 + /// Serializes this GalleryPhoto to a JSON map. 32 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 33 + 34 + /// Create a copy of GalleryPhoto 35 + /// with the given fields replaced by the non-null parameter values. 36 + @JsonKey(includeFromJson: false, includeToJson: false) 37 + $GalleryPhotoCopyWith<GalleryPhoto> get copyWith => 38 + throw _privateConstructorUsedError; 39 + } 40 + 41 + /// @nodoc 42 + abstract class $GalleryPhotoCopyWith<$Res> { 43 + factory $GalleryPhotoCopyWith( 44 + GalleryPhoto value, 45 + $Res Function(GalleryPhoto) then, 46 + ) = _$GalleryPhotoCopyWithImpl<$Res, GalleryPhoto>; 47 + @useResult 48 + $Res call({ 49 + String? uri, 50 + String? cid, 51 + String? thumb, 52 + String? fullsize, 53 + String? alt, 54 + AspectRatio? aspectRatio, 55 + }); 56 + 57 + $AspectRatioCopyWith<$Res>? get aspectRatio; 58 + } 59 + 60 + /// @nodoc 61 + class _$GalleryPhotoCopyWithImpl<$Res, $Val extends GalleryPhoto> 62 + implements $GalleryPhotoCopyWith<$Res> { 63 + _$GalleryPhotoCopyWithImpl(this._value, this._then); 64 + 65 + // ignore: unused_field 66 + final $Val _value; 67 + // ignore: unused_field 68 + final $Res Function($Val) _then; 69 + 70 + /// Create a copy of GalleryPhoto 71 + /// with the given fields replaced by the non-null parameter values. 72 + @pragma('vm:prefer-inline') 73 + @override 74 + $Res call({ 75 + Object? uri = freezed, 76 + Object? cid = freezed, 77 + Object? thumb = freezed, 78 + Object? fullsize = freezed, 79 + Object? alt = freezed, 80 + Object? aspectRatio = freezed, 81 + }) { 82 + return _then( 83 + _value.copyWith( 84 + uri: freezed == uri 85 + ? _value.uri 86 + : uri // ignore: cast_nullable_to_non_nullable 87 + as String?, 88 + cid: freezed == cid 89 + ? _value.cid 90 + : cid // ignore: cast_nullable_to_non_nullable 91 + as String?, 92 + thumb: freezed == thumb 93 + ? _value.thumb 94 + : thumb // ignore: cast_nullable_to_non_nullable 95 + as String?, 96 + fullsize: freezed == fullsize 97 + ? _value.fullsize 98 + : fullsize // ignore: cast_nullable_to_non_nullable 99 + as String?, 100 + alt: freezed == alt 101 + ? _value.alt 102 + : alt // ignore: cast_nullable_to_non_nullable 103 + as String?, 104 + aspectRatio: freezed == aspectRatio 105 + ? _value.aspectRatio 106 + : aspectRatio // ignore: cast_nullable_to_non_nullable 107 + as AspectRatio?, 108 + ) 109 + as $Val, 110 + ); 111 + } 112 + 113 + /// Create a copy of GalleryPhoto 114 + /// with the given fields replaced by the non-null parameter values. 115 + @override 116 + @pragma('vm:prefer-inline') 117 + $AspectRatioCopyWith<$Res>? get aspectRatio { 118 + if (_value.aspectRatio == null) { 119 + return null; 120 + } 121 + 122 + return $AspectRatioCopyWith<$Res>(_value.aspectRatio!, (value) { 123 + return _then(_value.copyWith(aspectRatio: value) as $Val); 124 + }); 125 + } 126 + } 127 + 128 + /// @nodoc 129 + abstract class _$$GalleryPhotoImplCopyWith<$Res> 130 + implements $GalleryPhotoCopyWith<$Res> { 131 + factory _$$GalleryPhotoImplCopyWith( 132 + _$GalleryPhotoImpl value, 133 + $Res Function(_$GalleryPhotoImpl) then, 134 + ) = __$$GalleryPhotoImplCopyWithImpl<$Res>; 135 + @override 136 + @useResult 137 + $Res call({ 138 + String? uri, 139 + String? cid, 140 + String? thumb, 141 + String? fullsize, 142 + String? alt, 143 + AspectRatio? aspectRatio, 144 + }); 145 + 146 + @override 147 + $AspectRatioCopyWith<$Res>? get aspectRatio; 148 + } 149 + 150 + /// @nodoc 151 + class __$$GalleryPhotoImplCopyWithImpl<$Res> 152 + extends _$GalleryPhotoCopyWithImpl<$Res, _$GalleryPhotoImpl> 153 + implements _$$GalleryPhotoImplCopyWith<$Res> { 154 + __$$GalleryPhotoImplCopyWithImpl( 155 + _$GalleryPhotoImpl _value, 156 + $Res Function(_$GalleryPhotoImpl) _then, 157 + ) : super(_value, _then); 158 + 159 + /// Create a copy of GalleryPhoto 160 + /// with the given fields replaced by the non-null parameter values. 161 + @pragma('vm:prefer-inline') 162 + @override 163 + $Res call({ 164 + Object? uri = freezed, 165 + Object? cid = freezed, 166 + Object? thumb = freezed, 167 + Object? fullsize = freezed, 168 + Object? alt = freezed, 169 + Object? aspectRatio = freezed, 170 + }) { 171 + return _then( 172 + _$GalleryPhotoImpl( 173 + uri: freezed == uri 174 + ? _value.uri 175 + : uri // ignore: cast_nullable_to_non_nullable 176 + as String?, 177 + cid: freezed == cid 178 + ? _value.cid 179 + : cid // ignore: cast_nullable_to_non_nullable 180 + as String?, 181 + thumb: freezed == thumb 182 + ? _value.thumb 183 + : thumb // ignore: cast_nullable_to_non_nullable 184 + as String?, 185 + fullsize: freezed == fullsize 186 + ? _value.fullsize 187 + : fullsize // ignore: cast_nullable_to_non_nullable 188 + as String?, 189 + alt: freezed == alt 190 + ? _value.alt 191 + : alt // ignore: cast_nullable_to_non_nullable 192 + as String?, 193 + aspectRatio: freezed == aspectRatio 194 + ? _value.aspectRatio 195 + : aspectRatio // ignore: cast_nullable_to_non_nullable 196 + as AspectRatio?, 197 + ), 198 + ); 199 + } 200 + } 201 + 202 + /// @nodoc 203 + @JsonSerializable() 204 + class _$GalleryPhotoImpl implements _GalleryPhoto { 205 + const _$GalleryPhotoImpl({ 206 + this.uri, 207 + this.cid, 208 + this.thumb, 209 + this.fullsize, 210 + this.alt, 211 + this.aspectRatio, 212 + }); 213 + 214 + factory _$GalleryPhotoImpl.fromJson(Map<String, dynamic> json) => 215 + _$$GalleryPhotoImplFromJson(json); 216 + 217 + @override 218 + final String? uri; 219 + @override 220 + final String? cid; 221 + @override 222 + final String? thumb; 223 + @override 224 + final String? fullsize; 225 + @override 226 + final String? alt; 227 + @override 228 + final AspectRatio? aspectRatio; 229 + 230 + @override 231 + String toString() { 232 + return 'GalleryPhoto(uri: $uri, cid: $cid, thumb: $thumb, fullsize: $fullsize, alt: $alt, aspectRatio: $aspectRatio)'; 233 + } 234 + 235 + @override 236 + bool operator ==(Object other) { 237 + return identical(this, other) || 238 + (other.runtimeType == runtimeType && 239 + other is _$GalleryPhotoImpl && 240 + (identical(other.uri, uri) || other.uri == uri) && 241 + (identical(other.cid, cid) || other.cid == cid) && 242 + (identical(other.thumb, thumb) || other.thumb == thumb) && 243 + (identical(other.fullsize, fullsize) || 244 + other.fullsize == fullsize) && 245 + (identical(other.alt, alt) || other.alt == alt) && 246 + (identical(other.aspectRatio, aspectRatio) || 247 + other.aspectRatio == aspectRatio)); 248 + } 249 + 250 + @JsonKey(includeFromJson: false, includeToJson: false) 251 + @override 252 + int get hashCode => 253 + Object.hash(runtimeType, uri, cid, thumb, fullsize, alt, aspectRatio); 254 + 255 + /// Create a copy of GalleryPhoto 256 + /// with the given fields replaced by the non-null parameter values. 257 + @JsonKey(includeFromJson: false, includeToJson: false) 258 + @override 259 + @pragma('vm:prefer-inline') 260 + _$$GalleryPhotoImplCopyWith<_$GalleryPhotoImpl> get copyWith => 261 + __$$GalleryPhotoImplCopyWithImpl<_$GalleryPhotoImpl>(this, _$identity); 262 + 263 + @override 264 + Map<String, dynamic> toJson() { 265 + return _$$GalleryPhotoImplToJson(this); 266 + } 267 + } 268 + 269 + abstract class _GalleryPhoto implements GalleryPhoto { 270 + const factory _GalleryPhoto({ 271 + final String? uri, 272 + final String? cid, 273 + final String? thumb, 274 + final String? fullsize, 275 + final String? alt, 276 + final AspectRatio? aspectRatio, 277 + }) = _$GalleryPhotoImpl; 278 + 279 + factory _GalleryPhoto.fromJson(Map<String, dynamic> json) = 280 + _$GalleryPhotoImpl.fromJson; 281 + 282 + @override 283 + String? get uri; 284 + @override 285 + String? get cid; 286 + @override 287 + String? get thumb; 288 + @override 289 + String? get fullsize; 290 + @override 291 + String? get alt; 292 + @override 293 + AspectRatio? get aspectRatio; 294 + 295 + /// Create a copy of GalleryPhoto 296 + /// with the given fields replaced by the non-null parameter values. 297 + @override 298 + @JsonKey(includeFromJson: false, includeToJson: false) 299 + _$$GalleryPhotoImplCopyWith<_$GalleryPhotoImpl> get copyWith => 300 + throw _privateConstructorUsedError; 301 + }
+29
lib/models/gallery_photo.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'gallery_photo.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$GalleryPhotoImpl _$$GalleryPhotoImplFromJson(Map<String, dynamic> json) => 10 + _$GalleryPhotoImpl( 11 + uri: json['uri'] as String?, 12 + cid: json['cid'] as String?, 13 + thumb: json['thumb'] as String?, 14 + fullsize: json['fullsize'] as String?, 15 + alt: json['alt'] as String?, 16 + aspectRatio: json['aspectRatio'] == null 17 + ? null 18 + : AspectRatio.fromJson(json['aspectRatio'] as Map<String, dynamic>), 19 + ); 20 + 21 + Map<String, dynamic> _$$GalleryPhotoImplToJson(_$GalleryPhotoImpl instance) => 22 + <String, dynamic>{ 23 + 'uri': instance.uri, 24 + 'cid': instance.cid, 25 + 'thumb': instance.thumb, 26 + 'fullsize': instance.fullsize, 27 + 'alt': instance.alt, 28 + 'aspectRatio': instance.aspectRatio, 29 + };
+11
lib/models/gallery_viewer.dart
···
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 + part 'gallery_viewer.freezed.dart'; 4 + part 'gallery_viewer.g.dart'; 5 + 6 + @freezed 7 + class GalleryViewer with _$GalleryViewer { 8 + const factory GalleryViewer({String? fav}) = _GalleryViewer; 9 + 10 + factory GalleryViewer.fromJson(Map<String, dynamic> json) => _$GalleryViewerFromJson(json); 11 + }
+167
lib/models/gallery_viewer.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 'gallery_viewer.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 + GalleryViewer _$GalleryViewerFromJson(Map<String, dynamic> json) { 19 + return _GalleryViewer.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$GalleryViewer { 24 + String? get fav => throw _privateConstructorUsedError; 25 + 26 + /// Serializes this GalleryViewer to a JSON map. 27 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 28 + 29 + /// Create a copy of GalleryViewer 30 + /// with the given fields replaced by the non-null parameter values. 31 + @JsonKey(includeFromJson: false, includeToJson: false) 32 + $GalleryViewerCopyWith<GalleryViewer> get copyWith => 33 + throw _privateConstructorUsedError; 34 + } 35 + 36 + /// @nodoc 37 + abstract class $GalleryViewerCopyWith<$Res> { 38 + factory $GalleryViewerCopyWith( 39 + GalleryViewer value, 40 + $Res Function(GalleryViewer) then, 41 + ) = _$GalleryViewerCopyWithImpl<$Res, GalleryViewer>; 42 + @useResult 43 + $Res call({String? fav}); 44 + } 45 + 46 + /// @nodoc 47 + class _$GalleryViewerCopyWithImpl<$Res, $Val extends GalleryViewer> 48 + implements $GalleryViewerCopyWith<$Res> { 49 + _$GalleryViewerCopyWithImpl(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 GalleryViewer 57 + /// with the given fields replaced by the non-null parameter values. 58 + @pragma('vm:prefer-inline') 59 + @override 60 + $Res call({Object? fav = freezed}) { 61 + return _then( 62 + _value.copyWith( 63 + fav: freezed == fav 64 + ? _value.fav 65 + : fav // ignore: cast_nullable_to_non_nullable 66 + as String?, 67 + ) 68 + as $Val, 69 + ); 70 + } 71 + } 72 + 73 + /// @nodoc 74 + abstract class _$$GalleryViewerImplCopyWith<$Res> 75 + implements $GalleryViewerCopyWith<$Res> { 76 + factory _$$GalleryViewerImplCopyWith( 77 + _$GalleryViewerImpl value, 78 + $Res Function(_$GalleryViewerImpl) then, 79 + ) = __$$GalleryViewerImplCopyWithImpl<$Res>; 80 + @override 81 + @useResult 82 + $Res call({String? fav}); 83 + } 84 + 85 + /// @nodoc 86 + class __$$GalleryViewerImplCopyWithImpl<$Res> 87 + extends _$GalleryViewerCopyWithImpl<$Res, _$GalleryViewerImpl> 88 + implements _$$GalleryViewerImplCopyWith<$Res> { 89 + __$$GalleryViewerImplCopyWithImpl( 90 + _$GalleryViewerImpl _value, 91 + $Res Function(_$GalleryViewerImpl) _then, 92 + ) : super(_value, _then); 93 + 94 + /// Create a copy of GalleryViewer 95 + /// with the given fields replaced by the non-null parameter values. 96 + @pragma('vm:prefer-inline') 97 + @override 98 + $Res call({Object? fav = freezed}) { 99 + return _then( 100 + _$GalleryViewerImpl( 101 + fav: freezed == fav 102 + ? _value.fav 103 + : fav // ignore: cast_nullable_to_non_nullable 104 + as String?, 105 + ), 106 + ); 107 + } 108 + } 109 + 110 + /// @nodoc 111 + @JsonSerializable() 112 + class _$GalleryViewerImpl implements _GalleryViewer { 113 + const _$GalleryViewerImpl({this.fav}); 114 + 115 + factory _$GalleryViewerImpl.fromJson(Map<String, dynamic> json) => 116 + _$$GalleryViewerImplFromJson(json); 117 + 118 + @override 119 + final String? fav; 120 + 121 + @override 122 + String toString() { 123 + return 'GalleryViewer(fav: $fav)'; 124 + } 125 + 126 + @override 127 + bool operator ==(Object other) { 128 + return identical(this, other) || 129 + (other.runtimeType == runtimeType && 130 + other is _$GalleryViewerImpl && 131 + (identical(other.fav, fav) || other.fav == fav)); 132 + } 133 + 134 + @JsonKey(includeFromJson: false, includeToJson: false) 135 + @override 136 + int get hashCode => Object.hash(runtimeType, fav); 137 + 138 + /// Create a copy of GalleryViewer 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 + _$$GalleryViewerImplCopyWith<_$GalleryViewerImpl> get copyWith => 144 + __$$GalleryViewerImplCopyWithImpl<_$GalleryViewerImpl>(this, _$identity); 145 + 146 + @override 147 + Map<String, dynamic> toJson() { 148 + return _$$GalleryViewerImplToJson(this); 149 + } 150 + } 151 + 152 + abstract class _GalleryViewer implements GalleryViewer { 153 + const factory _GalleryViewer({final String? fav}) = _$GalleryViewerImpl; 154 + 155 + factory _GalleryViewer.fromJson(Map<String, dynamic> json) = 156 + _$GalleryViewerImpl.fromJson; 157 + 158 + @override 159 + String? get fav; 160 + 161 + /// Create a copy of GalleryViewer 162 + /// with the given fields replaced by the non-null parameter values. 163 + @override 164 + @JsonKey(includeFromJson: false, includeToJson: false) 165 + _$$GalleryViewerImplCopyWith<_$GalleryViewerImpl> get copyWith => 166 + throw _privateConstructorUsedError; 167 + }
+13
lib/models/gallery_viewer.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'gallery_viewer.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$GalleryViewerImpl _$$GalleryViewerImplFromJson(Map<String, dynamic> json) => 10 + _$GalleryViewerImpl(fav: json['fav'] as String?); 11 + 12 + Map<String, dynamic> _$$GalleryViewerImplToJson(_$GalleryViewerImpl instance) => 13 + <String, dynamic>{'fav': instance.fav};
+17 -31
lib/models/notification.dart
··· 1 import 'profile.dart'; 2 3 - class Notification { 4 - final String uri; 5 - final String cid; 6 - final Profile author; 7 - final Map<String, dynamic> record; 8 - final String reason; 9 - final String? reasonSubject; 10 - final bool isRead; 11 - final String indexedAt; 12 13 - Notification({ 14 - required this.uri, 15 - required this.cid, 16 - required this.author, 17 - required this.record, 18 - required this.reason, 19 - this.reasonSubject, 20 - required this.isRead, 21 - required this.indexedAt, 22 - }); 23 24 - factory Notification.fromJson(Map<String, dynamic> json) { 25 - return Notification( 26 - uri: json['uri'] as String, 27 - cid: json['cid'] as String, 28 - author: Profile.fromJson(json['author'] as Map<String, dynamic>), 29 - record: json['record'] as Map<String, dynamic>, 30 - reason: json['reason'] as String, 31 - reasonSubject: json['reasonSubject'] as String?, 32 - isRead: json['isRead'] as bool? ?? false, 33 - indexedAt: json['indexedAt'] as String, 34 - ); 35 - } 36 }
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 + 3 import 'profile.dart'; 4 5 + part 'notification.freezed.dart'; 6 + part 'notification.g.dart'; 7 8 + @freezed 9 + class Notification with _$Notification { 10 + const factory Notification({ 11 + required String uri, 12 + required String cid, 13 + required Profile author, 14 + required Map<String, dynamic> record, 15 + required String reason, 16 + String? reasonSubject, 17 + required bool isRead, 18 + required String indexedAt, 19 + }) = _Notification; 20 21 + factory Notification.fromJson(Map<String, dynamic> json) => _$NotificationFromJson(json); 22 }
+352
lib/models/notification.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 'notification.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 + Notification _$NotificationFromJson(Map<String, dynamic> json) { 19 + return _Notification.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$Notification { 24 + String get uri => throw _privateConstructorUsedError; 25 + String get cid => throw _privateConstructorUsedError; 26 + Profile get author => throw _privateConstructorUsedError; 27 + Map<String, dynamic> get record => throw _privateConstructorUsedError; 28 + String get reason => throw _privateConstructorUsedError; 29 + String? get reasonSubject => throw _privateConstructorUsedError; 30 + bool get isRead => throw _privateConstructorUsedError; 31 + String get indexedAt => throw _privateConstructorUsedError; 32 + 33 + /// Serializes this Notification to a JSON map. 34 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 35 + 36 + /// Create a copy of Notification 37 + /// with the given fields replaced by the non-null parameter values. 38 + @JsonKey(includeFromJson: false, includeToJson: false) 39 + $NotificationCopyWith<Notification> get copyWith => 40 + throw _privateConstructorUsedError; 41 + } 42 + 43 + /// @nodoc 44 + abstract class $NotificationCopyWith<$Res> { 45 + factory $NotificationCopyWith( 46 + Notification value, 47 + $Res Function(Notification) then, 48 + ) = _$NotificationCopyWithImpl<$Res, Notification>; 49 + @useResult 50 + $Res call({ 51 + String uri, 52 + String cid, 53 + Profile author, 54 + Map<String, dynamic> record, 55 + String reason, 56 + String? reasonSubject, 57 + bool isRead, 58 + String indexedAt, 59 + }); 60 + 61 + $ProfileCopyWith<$Res> get author; 62 + } 63 + 64 + /// @nodoc 65 + class _$NotificationCopyWithImpl<$Res, $Val extends Notification> 66 + implements $NotificationCopyWith<$Res> { 67 + _$NotificationCopyWithImpl(this._value, this._then); 68 + 69 + // ignore: unused_field 70 + final $Val _value; 71 + // ignore: unused_field 72 + final $Res Function($Val) _then; 73 + 74 + /// Create a copy of Notification 75 + /// with the given fields replaced by the non-null parameter values. 76 + @pragma('vm:prefer-inline') 77 + @override 78 + $Res call({ 79 + Object? uri = null, 80 + Object? cid = null, 81 + Object? author = null, 82 + Object? record = null, 83 + Object? reason = null, 84 + Object? reasonSubject = freezed, 85 + Object? isRead = null, 86 + Object? indexedAt = null, 87 + }) { 88 + return _then( 89 + _value.copyWith( 90 + uri: null == uri 91 + ? _value.uri 92 + : uri // ignore: cast_nullable_to_non_nullable 93 + as String, 94 + cid: null == cid 95 + ? _value.cid 96 + : cid // ignore: cast_nullable_to_non_nullable 97 + as String, 98 + author: null == author 99 + ? _value.author 100 + : author // ignore: cast_nullable_to_non_nullable 101 + as Profile, 102 + record: null == record 103 + ? _value.record 104 + : record // ignore: cast_nullable_to_non_nullable 105 + as Map<String, dynamic>, 106 + reason: null == reason 107 + ? _value.reason 108 + : reason // ignore: cast_nullable_to_non_nullable 109 + as String, 110 + reasonSubject: freezed == reasonSubject 111 + ? _value.reasonSubject 112 + : reasonSubject // ignore: cast_nullable_to_non_nullable 113 + as String?, 114 + isRead: null == isRead 115 + ? _value.isRead 116 + : isRead // ignore: cast_nullable_to_non_nullable 117 + as bool, 118 + indexedAt: null == indexedAt 119 + ? _value.indexedAt 120 + : indexedAt // ignore: cast_nullable_to_non_nullable 121 + as String, 122 + ) 123 + as $Val, 124 + ); 125 + } 126 + 127 + /// Create a copy of Notification 128 + /// with the given fields replaced by the non-null parameter values. 129 + @override 130 + @pragma('vm:prefer-inline') 131 + $ProfileCopyWith<$Res> get author { 132 + return $ProfileCopyWith<$Res>(_value.author, (value) { 133 + return _then(_value.copyWith(author: value) as $Val); 134 + }); 135 + } 136 + } 137 + 138 + /// @nodoc 139 + abstract class _$$NotificationImplCopyWith<$Res> 140 + implements $NotificationCopyWith<$Res> { 141 + factory _$$NotificationImplCopyWith( 142 + _$NotificationImpl value, 143 + $Res Function(_$NotificationImpl) then, 144 + ) = __$$NotificationImplCopyWithImpl<$Res>; 145 + @override 146 + @useResult 147 + $Res call({ 148 + String uri, 149 + String cid, 150 + Profile author, 151 + Map<String, dynamic> record, 152 + String reason, 153 + String? reasonSubject, 154 + bool isRead, 155 + String indexedAt, 156 + }); 157 + 158 + @override 159 + $ProfileCopyWith<$Res> get author; 160 + } 161 + 162 + /// @nodoc 163 + class __$$NotificationImplCopyWithImpl<$Res> 164 + extends _$NotificationCopyWithImpl<$Res, _$NotificationImpl> 165 + implements _$$NotificationImplCopyWith<$Res> { 166 + __$$NotificationImplCopyWithImpl( 167 + _$NotificationImpl _value, 168 + $Res Function(_$NotificationImpl) _then, 169 + ) : super(_value, _then); 170 + 171 + /// Create a copy of Notification 172 + /// with the given fields replaced by the non-null parameter values. 173 + @pragma('vm:prefer-inline') 174 + @override 175 + $Res call({ 176 + Object? uri = null, 177 + Object? cid = null, 178 + Object? author = null, 179 + Object? record = null, 180 + Object? reason = null, 181 + Object? reasonSubject = freezed, 182 + Object? isRead = null, 183 + Object? indexedAt = null, 184 + }) { 185 + return _then( 186 + _$NotificationImpl( 187 + uri: null == uri 188 + ? _value.uri 189 + : uri // ignore: cast_nullable_to_non_nullable 190 + as String, 191 + cid: null == cid 192 + ? _value.cid 193 + : cid // ignore: cast_nullable_to_non_nullable 194 + as String, 195 + author: null == author 196 + ? _value.author 197 + : author // ignore: cast_nullable_to_non_nullable 198 + as Profile, 199 + record: null == record 200 + ? _value._record 201 + : record // ignore: cast_nullable_to_non_nullable 202 + as Map<String, dynamic>, 203 + reason: null == reason 204 + ? _value.reason 205 + : reason // ignore: cast_nullable_to_non_nullable 206 + as String, 207 + reasonSubject: freezed == reasonSubject 208 + ? _value.reasonSubject 209 + : reasonSubject // ignore: cast_nullable_to_non_nullable 210 + as String?, 211 + isRead: null == isRead 212 + ? _value.isRead 213 + : isRead // ignore: cast_nullable_to_non_nullable 214 + as bool, 215 + indexedAt: null == indexedAt 216 + ? _value.indexedAt 217 + : indexedAt // ignore: cast_nullable_to_non_nullable 218 + as String, 219 + ), 220 + ); 221 + } 222 + } 223 + 224 + /// @nodoc 225 + @JsonSerializable() 226 + class _$NotificationImpl implements _Notification { 227 + const _$NotificationImpl({ 228 + required this.uri, 229 + required this.cid, 230 + required this.author, 231 + required final Map<String, dynamic> record, 232 + required this.reason, 233 + this.reasonSubject, 234 + required this.isRead, 235 + required this.indexedAt, 236 + }) : _record = record; 237 + 238 + factory _$NotificationImpl.fromJson(Map<String, dynamic> json) => 239 + _$$NotificationImplFromJson(json); 240 + 241 + @override 242 + final String uri; 243 + @override 244 + final String cid; 245 + @override 246 + final Profile author; 247 + final Map<String, dynamic> _record; 248 + @override 249 + Map<String, dynamic> get record { 250 + if (_record is EqualUnmodifiableMapView) return _record; 251 + // ignore: implicit_dynamic_type 252 + return EqualUnmodifiableMapView(_record); 253 + } 254 + 255 + @override 256 + final String reason; 257 + @override 258 + final String? reasonSubject; 259 + @override 260 + final bool isRead; 261 + @override 262 + final String indexedAt; 263 + 264 + @override 265 + String toString() { 266 + return 'Notification(uri: $uri, cid: $cid, author: $author, record: $record, reason: $reason, reasonSubject: $reasonSubject, isRead: $isRead, indexedAt: $indexedAt)'; 267 + } 268 + 269 + @override 270 + bool operator ==(Object other) { 271 + return identical(this, other) || 272 + (other.runtimeType == runtimeType && 273 + other is _$NotificationImpl && 274 + (identical(other.uri, uri) || other.uri == uri) && 275 + (identical(other.cid, cid) || other.cid == cid) && 276 + (identical(other.author, author) || other.author == author) && 277 + const DeepCollectionEquality().equals(other._record, _record) && 278 + (identical(other.reason, reason) || other.reason == reason) && 279 + (identical(other.reasonSubject, reasonSubject) || 280 + other.reasonSubject == reasonSubject) && 281 + (identical(other.isRead, isRead) || other.isRead == isRead) && 282 + (identical(other.indexedAt, indexedAt) || 283 + other.indexedAt == indexedAt)); 284 + } 285 + 286 + @JsonKey(includeFromJson: false, includeToJson: false) 287 + @override 288 + int get hashCode => Object.hash( 289 + runtimeType, 290 + uri, 291 + cid, 292 + author, 293 + const DeepCollectionEquality().hash(_record), 294 + reason, 295 + reasonSubject, 296 + isRead, 297 + indexedAt, 298 + ); 299 + 300 + /// Create a copy of Notification 301 + /// with the given fields replaced by the non-null parameter values. 302 + @JsonKey(includeFromJson: false, includeToJson: false) 303 + @override 304 + @pragma('vm:prefer-inline') 305 + _$$NotificationImplCopyWith<_$NotificationImpl> get copyWith => 306 + __$$NotificationImplCopyWithImpl<_$NotificationImpl>(this, _$identity); 307 + 308 + @override 309 + Map<String, dynamic> toJson() { 310 + return _$$NotificationImplToJson(this); 311 + } 312 + } 313 + 314 + abstract class _Notification implements Notification { 315 + const factory _Notification({ 316 + required final String uri, 317 + required final String cid, 318 + required final Profile author, 319 + required final Map<String, dynamic> record, 320 + required final String reason, 321 + final String? reasonSubject, 322 + required final bool isRead, 323 + required final String indexedAt, 324 + }) = _$NotificationImpl; 325 + 326 + factory _Notification.fromJson(Map<String, dynamic> json) = 327 + _$NotificationImpl.fromJson; 328 + 329 + @override 330 + String get uri; 331 + @override 332 + String get cid; 333 + @override 334 + Profile get author; 335 + @override 336 + Map<String, dynamic> get record; 337 + @override 338 + String get reason; 339 + @override 340 + String? get reasonSubject; 341 + @override 342 + bool get isRead; 343 + @override 344 + String get indexedAt; 345 + 346 + /// Create a copy of Notification 347 + /// with the given fields replaced by the non-null parameter values. 348 + @override 349 + @JsonKey(includeFromJson: false, includeToJson: false) 350 + _$$NotificationImplCopyWith<_$NotificationImpl> get copyWith => 351 + throw _privateConstructorUsedError; 352 + }
+31
lib/models/notification.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'notification.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$NotificationImpl _$$NotificationImplFromJson(Map<String, dynamic> json) => 10 + _$NotificationImpl( 11 + uri: json['uri'] as String, 12 + cid: json['cid'] as String, 13 + author: Profile.fromJson(json['author'] as Map<String, dynamic>), 14 + record: json['record'] as Map<String, dynamic>, 15 + reason: json['reason'] as String, 16 + reasonSubject: json['reasonSubject'] as String?, 17 + isRead: json['isRead'] as bool, 18 + indexedAt: json['indexedAt'] as String, 19 + ); 20 + 21 + Map<String, dynamic> _$$NotificationImplToJson(_$NotificationImpl instance) => 22 + <String, dynamic>{ 23 + 'uri': instance.uri, 24 + 'cid': instance.cid, 25 + 'author': instance.author, 26 + 'record': instance.record, 27 + 'reason': instance.reason, 28 + 'reasonSubject': instance.reasonSubject, 29 + 'isRead': instance.isRead, 30 + 'indexedAt': instance.indexedAt, 31 + };
+16 -43
lib/models/profile.dart
··· 1 - class Profile { 2 - final String did; 3 - final String handle; 4 - final String displayName; 5 - final String description; 6 - final String avatar; 7 - final int followersCount; 8 - final int followsCount; 9 - final int galleryCount; 10 11 - Profile({ 12 - required this.did, 13 - required this.handle, 14 - required this.displayName, 15 - required this.description, 16 - required this.avatar, 17 - required this.followersCount, 18 - required this.followsCount, 19 - required this.galleryCount, 20 - }); 21 22 - factory Profile.fromJson(Map<String, dynamic> json) { 23 - return Profile( 24 - did: json['did'] ?? '', 25 - handle: json['handle'] ?? '', 26 - displayName: json['displayName'] ?? '', 27 - description: json['description'] ?? '', 28 - avatar: json['avatar'] ?? '', 29 - followersCount: json['followersCount'] ?? 0, 30 - followsCount: json['followsCount'] ?? 0, 31 - galleryCount: json['galleryCount'] ?? 0, 32 - ); 33 - } 34 35 - Map<String, dynamic> toJson() { 36 - return { 37 - 'did': did, 38 - 'handle': handle, 39 - 'displayName': displayName, 40 - 'description': description, 41 - 'avatar': avatar, 42 - 'followersCount': followersCount, 43 - 'followsCount': followsCount, 44 - 'galleryCount': galleryCount, 45 - }; 46 - } 47 }
··· 1 + import 'package:freezed_annotation/freezed_annotation.dart'; 2 3 + part 'profile.freezed.dart'; 4 + part 'profile.g.dart'; 5 6 + @freezed 7 + class Profile with _$Profile { 8 + const factory Profile({ 9 + required String did, 10 + required String handle, 11 + String? displayName, 12 + String? description, 13 + String? avatar, 14 + int? followersCount, 15 + int? followsCount, 16 + int? galleryCount, 17 + }) = _Profile; 18 19 + factory Profile.fromJson(Map<String, dynamic> json) => _$ProfileFromJson(json); 20 }
+329
lib/models/profile.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 'profile.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 + Profile _$ProfileFromJson(Map<String, dynamic> json) { 19 + return _Profile.fromJson(json); 20 + } 21 + 22 + /// @nodoc 23 + mixin _$Profile { 24 + String get did => throw _privateConstructorUsedError; 25 + String get handle => throw _privateConstructorUsedError; 26 + String? get displayName => throw _privateConstructorUsedError; 27 + String? get description => throw _privateConstructorUsedError; 28 + String? get avatar => throw _privateConstructorUsedError; 29 + int? get followersCount => throw _privateConstructorUsedError; 30 + int? get followsCount => throw _privateConstructorUsedError; 31 + int? get galleryCount => throw _privateConstructorUsedError; 32 + 33 + /// Serializes this Profile to a JSON map. 34 + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; 35 + 36 + /// Create a copy of Profile 37 + /// with the given fields replaced by the non-null parameter values. 38 + @JsonKey(includeFromJson: false, includeToJson: false) 39 + $ProfileCopyWith<Profile> get copyWith => throw _privateConstructorUsedError; 40 + } 41 + 42 + /// @nodoc 43 + abstract class $ProfileCopyWith<$Res> { 44 + factory $ProfileCopyWith(Profile value, $Res Function(Profile) then) = 45 + _$ProfileCopyWithImpl<$Res, Profile>; 46 + @useResult 47 + $Res call({ 48 + String did, 49 + String handle, 50 + String? displayName, 51 + String? description, 52 + String? avatar, 53 + int? followersCount, 54 + int? followsCount, 55 + int? galleryCount, 56 + }); 57 + } 58 + 59 + /// @nodoc 60 + class _$ProfileCopyWithImpl<$Res, $Val extends Profile> 61 + implements $ProfileCopyWith<$Res> { 62 + _$ProfileCopyWithImpl(this._value, this._then); 63 + 64 + // ignore: unused_field 65 + final $Val _value; 66 + // ignore: unused_field 67 + final $Res Function($Val) _then; 68 + 69 + /// Create a copy of Profile 70 + /// with the given fields replaced by the non-null parameter values. 71 + @pragma('vm:prefer-inline') 72 + @override 73 + $Res call({ 74 + Object? did = null, 75 + Object? handle = null, 76 + Object? displayName = freezed, 77 + Object? description = freezed, 78 + Object? avatar = freezed, 79 + Object? followersCount = freezed, 80 + Object? followsCount = freezed, 81 + Object? galleryCount = freezed, 82 + }) { 83 + return _then( 84 + _value.copyWith( 85 + did: null == did 86 + ? _value.did 87 + : did // ignore: cast_nullable_to_non_nullable 88 + as String, 89 + handle: null == handle 90 + ? _value.handle 91 + : handle // ignore: cast_nullable_to_non_nullable 92 + as String, 93 + displayName: freezed == displayName 94 + ? _value.displayName 95 + : displayName // ignore: cast_nullable_to_non_nullable 96 + as String?, 97 + description: freezed == description 98 + ? _value.description 99 + : description // ignore: cast_nullable_to_non_nullable 100 + as String?, 101 + avatar: freezed == avatar 102 + ? _value.avatar 103 + : avatar // ignore: cast_nullable_to_non_nullable 104 + as String?, 105 + followersCount: freezed == followersCount 106 + ? _value.followersCount 107 + : followersCount // ignore: cast_nullable_to_non_nullable 108 + as int?, 109 + followsCount: freezed == followsCount 110 + ? _value.followsCount 111 + : followsCount // ignore: cast_nullable_to_non_nullable 112 + as int?, 113 + galleryCount: freezed == galleryCount 114 + ? _value.galleryCount 115 + : galleryCount // ignore: cast_nullable_to_non_nullable 116 + as int?, 117 + ) 118 + as $Val, 119 + ); 120 + } 121 + } 122 + 123 + /// @nodoc 124 + abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> { 125 + factory _$$ProfileImplCopyWith( 126 + _$ProfileImpl value, 127 + $Res Function(_$ProfileImpl) then, 128 + ) = __$$ProfileImplCopyWithImpl<$Res>; 129 + @override 130 + @useResult 131 + $Res call({ 132 + String did, 133 + String handle, 134 + String? displayName, 135 + String? description, 136 + String? avatar, 137 + int? followersCount, 138 + int? followsCount, 139 + int? galleryCount, 140 + }); 141 + } 142 + 143 + /// @nodoc 144 + class __$$ProfileImplCopyWithImpl<$Res> 145 + extends _$ProfileCopyWithImpl<$Res, _$ProfileImpl> 146 + implements _$$ProfileImplCopyWith<$Res> { 147 + __$$ProfileImplCopyWithImpl( 148 + _$ProfileImpl _value, 149 + $Res Function(_$ProfileImpl) _then, 150 + ) : super(_value, _then); 151 + 152 + /// Create a copy of Profile 153 + /// with the given fields replaced by the non-null parameter values. 154 + @pragma('vm:prefer-inline') 155 + @override 156 + $Res call({ 157 + Object? did = null, 158 + Object? handle = null, 159 + Object? displayName = freezed, 160 + Object? description = freezed, 161 + Object? avatar = freezed, 162 + Object? followersCount = freezed, 163 + Object? followsCount = freezed, 164 + Object? galleryCount = freezed, 165 + }) { 166 + return _then( 167 + _$ProfileImpl( 168 + did: null == did 169 + ? _value.did 170 + : did // ignore: cast_nullable_to_non_nullable 171 + as String, 172 + handle: null == handle 173 + ? _value.handle 174 + : handle // ignore: cast_nullable_to_non_nullable 175 + as String, 176 + displayName: freezed == displayName 177 + ? _value.displayName 178 + : displayName // ignore: cast_nullable_to_non_nullable 179 + as String?, 180 + description: freezed == description 181 + ? _value.description 182 + : description // ignore: cast_nullable_to_non_nullable 183 + as String?, 184 + avatar: freezed == avatar 185 + ? _value.avatar 186 + : avatar // ignore: cast_nullable_to_non_nullable 187 + as String?, 188 + followersCount: freezed == followersCount 189 + ? _value.followersCount 190 + : followersCount // ignore: cast_nullable_to_non_nullable 191 + as int?, 192 + followsCount: freezed == followsCount 193 + ? _value.followsCount 194 + : followsCount // ignore: cast_nullable_to_non_nullable 195 + as int?, 196 + galleryCount: freezed == galleryCount 197 + ? _value.galleryCount 198 + : galleryCount // ignore: cast_nullable_to_non_nullable 199 + as int?, 200 + ), 201 + ); 202 + } 203 + } 204 + 205 + /// @nodoc 206 + @JsonSerializable() 207 + class _$ProfileImpl implements _Profile { 208 + const _$ProfileImpl({ 209 + required this.did, 210 + required this.handle, 211 + this.displayName, 212 + this.description, 213 + this.avatar, 214 + this.followersCount, 215 + this.followsCount, 216 + this.galleryCount, 217 + }); 218 + 219 + factory _$ProfileImpl.fromJson(Map<String, dynamic> json) => 220 + _$$ProfileImplFromJson(json); 221 + 222 + @override 223 + final String did; 224 + @override 225 + final String handle; 226 + @override 227 + final String? displayName; 228 + @override 229 + final String? description; 230 + @override 231 + final String? avatar; 232 + @override 233 + final int? followersCount; 234 + @override 235 + final int? followsCount; 236 + @override 237 + final int? galleryCount; 238 + 239 + @override 240 + String toString() { 241 + return 'Profile(did: $did, handle: $handle, displayName: $displayName, description: $description, avatar: $avatar, followersCount: $followersCount, followsCount: $followsCount, galleryCount: $galleryCount)'; 242 + } 243 + 244 + @override 245 + bool operator ==(Object other) { 246 + return identical(this, other) || 247 + (other.runtimeType == runtimeType && 248 + other is _$ProfileImpl && 249 + (identical(other.did, did) || other.did == did) && 250 + (identical(other.handle, handle) || other.handle == handle) && 251 + (identical(other.displayName, displayName) || 252 + other.displayName == displayName) && 253 + (identical(other.description, description) || 254 + other.description == description) && 255 + (identical(other.avatar, avatar) || other.avatar == avatar) && 256 + (identical(other.followersCount, followersCount) || 257 + other.followersCount == followersCount) && 258 + (identical(other.followsCount, followsCount) || 259 + other.followsCount == followsCount) && 260 + (identical(other.galleryCount, galleryCount) || 261 + other.galleryCount == galleryCount)); 262 + } 263 + 264 + @JsonKey(includeFromJson: false, includeToJson: false) 265 + @override 266 + int get hashCode => Object.hash( 267 + runtimeType, 268 + did, 269 + handle, 270 + displayName, 271 + description, 272 + avatar, 273 + followersCount, 274 + followsCount, 275 + galleryCount, 276 + ); 277 + 278 + /// Create a copy of Profile 279 + /// with the given fields replaced by the non-null parameter values. 280 + @JsonKey(includeFromJson: false, includeToJson: false) 281 + @override 282 + @pragma('vm:prefer-inline') 283 + _$$ProfileImplCopyWith<_$ProfileImpl> get copyWith => 284 + __$$ProfileImplCopyWithImpl<_$ProfileImpl>(this, _$identity); 285 + 286 + @override 287 + Map<String, dynamic> toJson() { 288 + return _$$ProfileImplToJson(this); 289 + } 290 + } 291 + 292 + abstract class _Profile implements Profile { 293 + const factory _Profile({ 294 + required final String did, 295 + required final String handle, 296 + final String? displayName, 297 + final String? description, 298 + final String? avatar, 299 + final int? followersCount, 300 + final int? followsCount, 301 + final int? galleryCount, 302 + }) = _$ProfileImpl; 303 + 304 + factory _Profile.fromJson(Map<String, dynamic> json) = _$ProfileImpl.fromJson; 305 + 306 + @override 307 + String get did; 308 + @override 309 + String get handle; 310 + @override 311 + String? get displayName; 312 + @override 313 + String? get description; 314 + @override 315 + String? get avatar; 316 + @override 317 + int? get followersCount; 318 + @override 319 + int? get followsCount; 320 + @override 321 + int? get galleryCount; 322 + 323 + /// Create a copy of Profile 324 + /// with the given fields replaced by the non-null parameter values. 325 + @override 326 + @JsonKey(includeFromJson: false, includeToJson: false) 327 + _$$ProfileImplCopyWith<_$ProfileImpl> get copyWith => 328 + throw _privateConstructorUsedError; 329 + }
+31
lib/models/profile.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'profile.dart'; 4 + 5 + // ************************************************************************** 6 + // JsonSerializableGenerator 7 + // ************************************************************************** 8 + 9 + _$ProfileImpl _$$ProfileImplFromJson(Map<String, dynamic> json) => 10 + _$ProfileImpl( 11 + did: json['did'] as String, 12 + handle: json['handle'] as String, 13 + displayName: json['displayName'] as String?, 14 + description: json['description'] as String?, 15 + avatar: json['avatar'] as String?, 16 + followersCount: (json['followersCount'] as num?)?.toInt(), 17 + followsCount: (json['followsCount'] as num?)?.toInt(), 18 + galleryCount: (json['galleryCount'] as num?)?.toInt(), 19 + ); 20 + 21 + Map<String, dynamic> _$$ProfileImplToJson(_$ProfileImpl instance) => 22 + <String, dynamic>{ 23 + 'did': instance.did, 24 + 'handle': instance.handle, 25 + 'displayName': instance.displayName, 26 + 'description': instance.description, 27 + 'avatar': instance.avatar, 28 + 'followersCount': instance.followersCount, 29 + 'followsCount': instance.followsCount, 30 + 'galleryCount': instance.galleryCount, 31 + };
+51
lib/providers/gallery_cache_provider.dart
···
··· 1 + import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 + 3 + import '../api.dart'; 4 + import '../models/gallery.dart'; 5 + 6 + part 'gallery_cache_provider.g.dart'; 7 + 8 + /// Holds a cache of galleries by URI. 9 + @Riverpod(keepAlive: true) 10 + class GalleryCache extends _$GalleryCache { 11 + @override 12 + Map<String, Gallery> build() => {}; 13 + 14 + void setGalleries(List<Gallery> galleries) { 15 + final newMap = {for (final g in galleries) g.uri: g}; 16 + state = {...state, ...newMap}; 17 + } 18 + 19 + void setGallery(Gallery gallery) { 20 + state = {...state, gallery.uri: gallery}; 21 + } 22 + 23 + void removeGallery(String uri) { 24 + final newState = {...state}..remove(uri); 25 + state = newState; 26 + } 27 + 28 + Gallery? getGallery(String uri) => state[uri]; 29 + 30 + Future<void> toggleFavorite(String uri) async { 31 + // Fetch the latest gallery from the API to ensure up-to-date favorite state 32 + final latestGallery = await apiService.getGallery(uri: uri); 33 + if (latestGallery == null) return; 34 + final isCurrentlyFav = latestGallery.viewer?.fav != null; 35 + final isFav = !isCurrentlyFav; 36 + bool success = false; 37 + String? newFavUri; 38 + if (isFav) { 39 + newFavUri = await apiService.createFavorite(galleryUri: uri); 40 + success = newFavUri != null; 41 + } else { 42 + success = await apiService.deleteRecord(latestGallery.viewer?.fav ?? uri); 43 + newFavUri = null; 44 + } 45 + if (success) { 46 + final newCount = (latestGallery.favCount ?? 0) + (isFav ? 1 : -1); 47 + final updatedViewer = latestGallery.viewer?.copyWith(fav: newFavUri); 48 + state = {...state, uri: latestGallery.copyWith(favCount: newCount, viewer: updatedViewer)}; 49 + } 50 + } 51 + }
+28
lib/providers/gallery_cache_provider.g.dart
···
··· 1 + // GENERATED CODE - DO NOT MODIFY BY HAND 2 + 3 + part of 'gallery_cache_provider.dart'; 4 + 5 + // ************************************************************************** 6 + // RiverpodGenerator 7 + // ************************************************************************** 8 + 9 + String _$galleryCacheHash() => r'dc64ef86246ea4ac742837cfcc8a9353375f2144'; 10 + 11 + /// Holds a cache of galleries by URI. 12 + /// 13 + /// Copied from [GalleryCache]. 14 + @ProviderFor(GalleryCache) 15 + final galleryCacheProvider = 16 + NotifierProvider<GalleryCache, Map<String, Gallery>>.internal( 17 + GalleryCache.new, 18 + name: r'galleryCacheProvider', 19 + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') 20 + ? null 21 + : _$galleryCacheHash, 22 + dependencies: null, 23 + allTransitiveDependencies: null, 24 + ); 25 + 26 + typedef _$GalleryCache = Notifier<Map<String, Gallery>>; 27 + // ignore_for_file: type=lint 28 + // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
+12 -7
lib/screens/comments_page.dart
··· 3 import 'package:grain/api.dart'; 4 import 'package:grain/models/comment.dart'; 5 import 'package:grain/models/gallery.dart'; 6 import 'package:grain/screens/profile_page.dart'; 7 import 'package:grain/utils.dart'; 8 import 'package:grain/widgets/app_image.dart'; ··· 169 padding: const EdgeInsets.fromLTRB(12, 12, 12, 100), 170 children: [ 171 if (_gallery != null) 172 - Text(_gallery!.title, style: theme.textTheme.titleMedium), 173 const SizedBox(height: 12), 174 _CommentsList( 175 comments: _comments, ··· 509 }, 510 ), 511 if (comment.focus != null && 512 - (comment.focus!.thumb.isNotEmpty || comment.focus!.fullsize.isNotEmpty)) 513 Padding( 514 padding: const EdgeInsets.only(top: 8.0), 515 child: Align( ··· 517 child: ConstrainedBox( 518 constraints: const BoxConstraints(maxWidth: 180, maxHeight: 180), 519 child: AspectRatio( 520 - aspectRatio: (comment.focus!.width > 0 && comment.focus!.height > 0) 521 - ? comment.focus!.width / comment.focus!.height 522 : 1.0, 523 child: ClipRRect( 524 borderRadius: BorderRadius.circular(8), ··· 531 thumb: comment.focus!.thumb, 532 fullsize: comment.focus!.fullsize, 533 alt: comment.focus!.alt, 534 - width: comment.focus!.width, 535 - height: comment.focus!.height, 536 ), 537 ) 538 : null, 539 child: AppImage( 540 - url: comment.focus!.thumb.isNotEmpty 541 ? comment.focus!.thumb 542 : comment.focus!.fullsize, 543 fit: BoxFit.cover,
··· 3 import 'package:grain/api.dart'; 4 import 'package:grain/models/comment.dart'; 5 import 'package:grain/models/gallery.dart'; 6 + import 'package:grain/models/gallery_photo.dart'; 7 import 'package:grain/screens/profile_page.dart'; 8 import 'package:grain/utils.dart'; 9 import 'package:grain/widgets/app_image.dart'; ··· 170 padding: const EdgeInsets.fromLTRB(12, 12, 12, 100), 171 children: [ 172 if (_gallery != null) 173 + Text(_gallery!.title ?? '', style: theme.textTheme.titleMedium), 174 const SizedBox(height: 12), 175 _CommentsList( 176 comments: _comments, ··· 510 }, 511 ), 512 if (comment.focus != null && 513 + ((comment.focus!.thumb?.isNotEmpty ?? false) || 514 + (comment.focus!.fullsize?.isNotEmpty ?? false))) 515 Padding( 516 padding: const EdgeInsets.only(top: 8.0), 517 child: Align( ··· 519 child: ConstrainedBox( 520 constraints: const BoxConstraints(maxWidth: 180, maxHeight: 180), 521 child: AspectRatio( 522 + aspectRatio: 523 + (comment.focus!.aspectRatio != null && 524 + (comment.focus!.aspectRatio!.width > 0 && 525 + comment.focus!.aspectRatio!.height > 0)) 526 + ? comment.focus!.aspectRatio!.width / 527 + comment.focus!.aspectRatio!.height 528 : 1.0, 529 child: ClipRRect( 530 borderRadius: BorderRadius.circular(8), ··· 537 thumb: comment.focus!.thumb, 538 fullsize: comment.focus!.fullsize, 539 alt: comment.focus!.alt, 540 + aspectRatio: comment.focus!.aspectRatio, 541 ), 542 ) 543 : null, 544 child: AppImage( 545 + url: (comment.focus!.thumb?.isNotEmpty ?? false) 546 ? comment.focus!.thumb 547 : comment.focus!.fullsize, 548 fit: BoxFit.cover,
+2 -2
lib/screens/explore_page.dart
··· 123 itemBuilder: (context, index) { 124 final profile = results[index]; 125 return ListTile( 126 - leading: profile.avatar.isNotEmpty 127 ? ClipOval( 128 child: AppImage(url: profile.avatar, width: 32, height: 32, fit: BoxFit.cover), 129 ) ··· 133 child: Icon(Icons.account_circle, color: theme.iconTheme.color), 134 ), 135 title: Text( 136 - profile.displayName.isNotEmpty ? profile.displayName : '@${profile.handle}', 137 style: theme.textTheme.bodyLarge, 138 ), 139 subtitle: profile.handle.isNotEmpty
··· 123 itemBuilder: (context, index) { 124 final profile = results[index]; 125 return ListTile( 126 + leading: profile.avatar?.isNotEmpty == true 127 ? ClipOval( 128 child: AppImage(url: profile.avatar, width: 32, height: 32, fit: BoxFit.cover), 129 ) ··· 133 child: Icon(Icons.account_circle, color: theme.iconTheme.color), 134 ), 135 title: Text( 136 + profile.displayName?.isNotEmpty == true ? profile.displayName! : '@${profile.handle}', 137 style: theme.textTheme.bodyLarge, 138 ), 139 subtitle: profile.handle.isNotEmpty
+36 -32
lib/screens/gallery_page.dart
··· 1 - import 'package:cached_network_image/cached_network_image.dart'; 2 import 'package:flutter/material.dart'; 3 import 'package:grain/api.dart'; 4 - import 'package:grain/models/gallery.dart'; 5 import 'package:grain/screens/create_gallery_page.dart'; 6 import 'package:grain/screens/profile_page.dart'; 7 import 'package:grain/widgets/app_image.dart'; ··· 10 import 'package:grain/widgets/gallery_photo_view.dart'; 11 import 'package:grain/widgets/justified_gallery_view.dart'; 12 13 - class GalleryPage extends StatefulWidget { 14 final String uri; 15 final String? currentUserDid; 16 const GalleryPage({super.key, required this.uri, this.currentUserDid}); 17 18 @override 19 - State<GalleryPage> createState() => _GalleryPageState(); 20 } 21 22 - class _GalleryPageState extends State<GalleryPage> { 23 - Gallery? _gallery; 24 bool _loading = true; 25 bool _error = false; 26 GalleryPhoto? _selectedPhoto; ··· 29 @override 30 void initState() { 31 super.initState(); 32 - _fetchGallery(); 33 } 34 35 - Future<void> _fetchGallery() async { 36 setState(() { 37 _loading = true; 38 _error = false; 39 }); 40 try { 41 final gallery = await apiService.getGallery(uri: widget.uri); 42 - setState(() { 43 - _gallery = gallery; 44 - _loading = false; 45 - }); 46 - // Pre-cache thumbnails and fullsize images in the background 47 if (gallery != null) { 48 - Future.microtask(() { 49 - for (final item in gallery.items) { 50 - if (item.thumb.isNotEmpty) { 51 - precacheImage(CachedNetworkImageProvider(item.thumb), context); 52 - } 53 - if (item.fullsize.isNotEmpty) { 54 - precacheImage(CachedNetworkImageProvider(item.fullsize), context); 55 - } 56 - } 57 }); 58 } 59 } catch (e) { ··· 67 @override 68 Widget build(BuildContext context) { 69 final theme = Theme.of(context); 70 if (_loading) { 71 return Scaffold( 72 backgroundColor: theme.scaffoldBackgroundColor, ··· 75 ), 76 ); 77 } 78 - if (_error || _gallery == null) { 79 return Scaffold( 80 backgroundColor: theme.scaffoldBackgroundColor, 81 body: const Center(child: Text('Failed to load gallery.')), 82 ); 83 } 84 - final gallery = _gallery!; 85 final isLoggedIn = widget.currentUserDid != null; 86 - final galleryItems = gallery.items.where((item) => item.thumb.isNotEmpty).toList(); 87 88 return Stack( 89 children: [ ··· 113 isScrollControlled: true, 114 builder: (context) => CreateGalleryPage(gallery: gallery), 115 ); 116 - _fetchGallery(); 117 }, 118 ), 119 ], ··· 127 children: [ 128 const SizedBox(height: 10), 129 Text( 130 - gallery.title.isNotEmpty ? gallery.title : 'Gallery', 131 style: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w600), 132 ), 133 const SizedBox(height: 10), ··· 150 backgroundColor: theme.colorScheme.surfaceContainerHighest, 151 backgroundImage: 152 gallery.creator?.avatar != null && 153 - gallery.creator!.avatar.isNotEmpty 154 ? null 155 : null, 156 - child: (gallery.creator == null || gallery.creator!.avatar.isEmpty) 157 ? Icon( 158 Icons.account_circle, 159 size: 24, ··· 161 ) 162 : ClipOval( 163 child: AppImage( 164 - url: gallery.creator!.avatar, 165 width: 36, 166 height: 36, 167 fit: BoxFit.cover, ··· 212 ), 213 ), 214 const SizedBox(height: 12), 215 - if (gallery.description.isNotEmpty) 216 Padding( 217 padding: const EdgeInsets.symmetric(horizontal: 8).copyWith(bottom: 8), 218 child: FacetedText( 219 - text: gallery.description, 220 facets: gallery.facets, 221 style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurface), 222 linkStyle: theme.textTheme.bodyMedium?.copyWith(
··· 1 import 'package:flutter/material.dart'; 2 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:grain/api.dart'; 4 + import 'package:grain/models/gallery_photo.dart'; 5 + import 'package:grain/providers/gallery_cache_provider.dart'; 6 import 'package:grain/screens/create_gallery_page.dart'; 7 import 'package:grain/screens/profile_page.dart'; 8 import 'package:grain/widgets/app_image.dart'; ··· 11 import 'package:grain/widgets/gallery_photo_view.dart'; 12 import 'package:grain/widgets/justified_gallery_view.dart'; 13 14 + class GalleryPage extends ConsumerStatefulWidget { 15 final String uri; 16 final String? currentUserDid; 17 const GalleryPage({super.key, required this.uri, this.currentUserDid}); 18 19 @override 20 + ConsumerState<GalleryPage> createState() => _GalleryPageState(); 21 } 22 23 + class _GalleryPageState extends ConsumerState<GalleryPage> { 24 bool _loading = true; 25 bool _error = false; 26 GalleryPhoto? _selectedPhoto; ··· 29 @override 30 void initState() { 31 super.initState(); 32 + _maybeFetchGallery(); 33 } 34 35 + Future<void> _maybeFetchGallery() async { 36 + final cached = ref.read(galleryCacheProvider)[widget.uri]; 37 + if (cached != null) { 38 + setState(() { 39 + _loading = false; 40 + _error = false; 41 + }); 42 + return; 43 + } 44 setState(() { 45 _loading = true; 46 _error = false; 47 }); 48 try { 49 final gallery = await apiService.getGallery(uri: widget.uri); 50 if (gallery != null) { 51 + ref.read(galleryCacheProvider.notifier).setGallery(gallery); 52 + setState(() { 53 + _loading = false; 54 + }); 55 + } else { 56 + setState(() { 57 + _error = true; 58 + _loading = false; 59 }); 60 } 61 } catch (e) { ··· 69 @override 70 Widget build(BuildContext context) { 71 final theme = Theme.of(context); 72 + final gallery = ref.watch(galleryCacheProvider)[widget.uri]; 73 if (_loading) { 74 return Scaffold( 75 backgroundColor: theme.scaffoldBackgroundColor, ··· 78 ), 79 ); 80 } 81 + if (_error || gallery == null) { 82 return Scaffold( 83 backgroundColor: theme.scaffoldBackgroundColor, 84 body: const Center(child: Text('Failed to load gallery.')), 85 ); 86 } 87 final isLoggedIn = widget.currentUserDid != null; 88 + final galleryItems = gallery.items.where((item) => item.thumb?.isNotEmpty ?? false).toList(); 89 90 return Stack( 91 children: [ ··· 115 isScrollControlled: true, 116 builder: (context) => CreateGalleryPage(gallery: gallery), 117 ); 118 + _maybeFetchGallery(); 119 }, 120 ), 121 ], ··· 129 children: [ 130 const SizedBox(height: 10), 131 Text( 132 + gallery.title?.isNotEmpty == true ? gallery.title! : 'Gallery', 133 style: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w600), 134 ), 135 const SizedBox(height: 10), ··· 152 backgroundColor: theme.colorScheme.surfaceContainerHighest, 153 backgroundImage: 154 gallery.creator?.avatar != null && 155 + gallery.creator!.avatar?.isNotEmpty == true 156 ? null 157 : null, 158 + child: 159 + (gallery.creator == null || 160 + (gallery.creator!.avatar?.isNotEmpty != true)) 161 ? Icon( 162 Icons.account_circle, 163 size: 24, ··· 165 ) 166 : ClipOval( 167 child: AppImage( 168 + url: gallery.creator!.avatar!, 169 width: 36, 170 height: 36, 171 fit: BoxFit.cover, ··· 216 ), 217 ), 218 const SizedBox(height: 12), 219 + if ((gallery.description?.isNotEmpty ?? false)) 220 Padding( 221 padding: const EdgeInsets.symmetric(horizontal: 8).copyWith(bottom: 8), 222 child: FacetedText( 223 + text: gallery.description ?? '', 224 facets: gallery.facets, 225 style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.onSurface), 226 linkStyle: theme.textTheme.bodyMedium?.copyWith(
+152 -290
lib/screens/home_page.dart
··· 1 import 'package:flutter/material.dart'; 2 import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 import 'package:grain/api.dart'; 4 - import 'package:grain/models/gallery.dart'; 5 import 'package:grain/screens/create_gallery_page.dart'; 6 - import 'package:grain/widgets/app_image.dart'; 7 import 'package:grain/widgets/app_version_text.dart'; 8 import 'package:grain/widgets/bottom_nav_bar.dart'; 9 import 'package:grain/widgets/timeline_item.dart'; 10 11 import 'explore_page.dart'; 12 import 'log_page.dart'; 13 import 'notifications_page.dart'; 14 import 'profile_page.dart'; 15 16 - class TimelineItem { 17 - final Gallery gallery; 18 - TimelineItem({required this.gallery}); 19 - factory TimelineItem.fromGallery(Gallery gallery) { 20 - return TimelineItem(gallery: gallery); 21 - } 22 - } 23 - 24 class MyHomePage extends StatefulWidget { 25 final String title; 26 final VoidCallback? onSignOut; ··· 34 bool showProfile = false; 35 bool showNotifications = false; 36 bool showExplore = false; 37 - List<TimelineItem> _timeline = []; 38 - List<TimelineItem> _followingTimeline = []; 39 bool _timelineLoading = true; 40 bool _followingTimelineLoading = false; 41 int _tabIndex = 0; // 0 = Timeline, 1 = Following ··· 49 } 50 51 Future<void> _fetchTimeline({String? algorithm}) async { 52 if (algorithm == "following") { 53 setState(() { 54 _followingTimelineLoading = true; 55 }); 56 try { 57 final galleries = await apiService.getTimeline(algorithm: algorithm); 58 setState(() { 59 - _followingTimeline = galleries.map((g) => TimelineItem.fromGallery(g)).toList(); 60 _followingTimelineLoading = false; 61 }); 62 } catch (e) { 63 setState(() { 64 - _followingTimeline = []; 65 _followingTimelineLoading = false; 66 }); 67 } ··· 71 }); 72 try { 73 final galleries = await apiService.getTimeline(algorithm: algorithm); 74 setState(() { 75 - _timeline = galleries.map((g) => TimelineItem.fromGallery(g)).toList(); 76 _timelineLoading = false; 77 }); 78 } catch (e) { 79 setState(() { 80 - _timeline = []; 81 _timelineLoading = false; 82 }); 83 } ··· 128 129 Widget _buildTimelineSliver(BuildContext context, {bool following = false}) { 130 final loading = following ? _followingTimelineLoading : _timelineLoading; 131 - final timeline = following ? _followingTimeline : _timeline; 132 return CustomScrollView( 133 key: PageStorageKey(following ? 'following' : 'timeline'), 134 slivers: [ 135 SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)), 136 - if (timeline.isEmpty && loading) 137 SliverFillRemaining( 138 hasScrollBody: false, 139 child: Center( ··· 143 ), 144 ), 145 ), 146 - if (timeline.isEmpty && !loading) 147 SliverFillRemaining( 148 hasScrollBody: false, 149 child: Center( 150 child: Text(following ? 'No following timeline items.' : 'No timeline items.'), 151 ), 152 ), 153 - if (timeline.isNotEmpty) 154 SliverList( 155 delegate: SliverChildBuilderDelegate((context, index) { 156 - final item = timeline[index]; 157 - return TimelineItemWidget(gallery: item.gallery); 158 - }, childCount: timeline.length), 159 ), 160 ], 161 ); 162 } 163 164 - @override 165 - Widget build(BuildContext context) { 166 - final theme = Theme.of(context); 167 - if (apiService.currentUser == null) { 168 - return Scaffold( 169 - body: Center( 170 - child: CircularProgressIndicator(strokeWidth: 2, color: theme.colorScheme.primary), 171 - ), 172 - ); 173 - } 174 - // Home page: show tabs 175 - if (!showProfile && !showNotifications && !showExplore) { 176 - if (_tabController == null) _initTabController(); 177 - return Scaffold( 178 - drawer: Drawer( 179 - child: ListView( 180 - padding: EdgeInsets.zero, 181 - children: [ 182 - Container( 183 - height: 250, 184 - decoration: BoxDecoration( 185 - color: theme.scaffoldBackgroundColor, 186 - border: Border(bottom: BorderSide(color: theme.dividerColor, width: 1)), 187 ), 188 - padding: const EdgeInsets.fromLTRB(16, 115, 16, 16), 189 - child: Column( 190 - crossAxisAlignment: CrossAxisAlignment.start, 191 - mainAxisAlignment: MainAxisAlignment.center, 192 children: [ 193 - CircleAvatar( 194 - radius: 22, 195 - backgroundColor: theme.scaffoldBackgroundColor, 196 - child: ClipOval( 197 - child: AppImage( 198 - url: apiService.currentUser!.avatar, 199 - width: 44, 200 - height: 44, 201 - fit: BoxFit.cover, 202 - ), 203 ), 204 ), 205 - const SizedBox(height: 6), 206 Text( 207 - apiService.currentUser?.displayName ?? '', 208 - style: theme.textTheme.bodyLarge?.copyWith( 209 fontWeight: FontWeight.bold, 210 color: theme.colorScheme.onSurface, 211 ), 212 ), 213 - if (apiService.currentUser?.handle != null) 214 - Text( 215 - '@${apiService.currentUser!.handle}', 216 - style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), 217 - ), 218 - const SizedBox(height: 6), 219 - Row( 220 - mainAxisAlignment: MainAxisAlignment.start, 221 - children: [ 222 - Text( 223 - (apiService.currentUser?.followersCount ?? 0).toString(), 224 - style: theme.textTheme.bodyMedium?.copyWith( 225 - fontWeight: FontWeight.bold, 226 - color: theme.colorScheme.onSurface, 227 - ), 228 - ), 229 - const SizedBox(width: 4), 230 - Text( 231 - 'Followers', 232 - style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), 233 - ), 234 - const SizedBox(width: 16), 235 - Text( 236 - (apiService.currentUser?.followsCount ?? 0).toString(), 237 - style: theme.textTheme.bodyMedium?.copyWith( 238 - fontWeight: FontWeight.bold, 239 - color: theme.colorScheme.onSurface, 240 - ), 241 - ), 242 - const SizedBox(width: 4), 243 - Text( 244 - 'Following', 245 - style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), 246 - ), 247 - ], 248 ), 249 ], 250 ), 251 - ), 252 - ListTile( 253 - leading: const Icon(FontAwesomeIcons.house), 254 - title: const Text('Home'), 255 - onTap: () { 256 - Navigator.pop(context); 257 - setState(() { 258 - showProfile = false; 259 - showNotifications = false; 260 - showExplore = false; 261 - }); 262 - }, 263 - ), 264 - ListTile( 265 - leading: const Icon(FontAwesomeIcons.magnifyingGlass), 266 - title: const Text('Explore'), 267 - onTap: () { 268 - Navigator.pop(context); 269 - setState(() { 270 - showExplore = true; 271 - showProfile = false; 272 - showNotifications = false; 273 - }); 274 - }, 275 - ), 276 - ListTile( 277 - leading: const Icon(FontAwesomeIcons.user), 278 - title: const Text('Profile'), 279 - onTap: () { 280 - Navigator.pop(context); 281 - setState(() { 282 - showProfile = true; 283 - showNotifications = false; 284 - showExplore = false; 285 - }); 286 - }, 287 - ), 288 - ListTile( 289 - leading: const Icon(FontAwesomeIcons.list), 290 - title: const Text('Logs'), 291 - onTap: () { 292 - Navigator.pop(context); 293 - Navigator.of( 294 - context, 295 - ).push(MaterialPageRoute(builder: (context) => const LogPage())); 296 - }, 297 - ), 298 - const SizedBox(height: 16), 299 - Padding( 300 - padding: const EdgeInsets.only(bottom: 16.0), 301 - child: Center(child: AppVersionText()), 302 - ), 303 - ], 304 ), 305 ), 306 body: NestedScrollView( 307 floatHeaderSlivers: true, 308 headerSliverBuilder: (context, innerBoxIsScrolled) => [ ··· 415 } 416 // Explore, Notifications, Profile: no tabs, no TabController 417 return Scaffold( 418 - drawer: Drawer( 419 - child: ListView( 420 - padding: EdgeInsets.zero, 421 - children: [ 422 - Container( 423 - decoration: BoxDecoration( 424 - color: theme.scaffoldBackgroundColor, 425 - border: Border(bottom: BorderSide(color: theme.dividerColor, width: 1)), 426 - ), 427 - padding: const EdgeInsets.fromLTRB(16, 24, 16, 16), 428 - child: Column( 429 - crossAxisAlignment: CrossAxisAlignment.start, 430 - mainAxisAlignment: MainAxisAlignment.center, 431 - children: [ 432 - CircleAvatar( 433 - radius: 22, 434 - backgroundColor: theme.scaffoldBackgroundColor, 435 - child: ClipOval( 436 - child: AppImage( 437 - url: apiService.currentUser!.avatar, 438 - width: 24, 439 - height: 24, 440 - fit: BoxFit.cover, 441 - ), 442 - ), 443 - ), 444 - const SizedBox(height: 6), 445 - Text( 446 - apiService.currentUser?.displayName ?? '', 447 - style: theme.textTheme.bodyLarge?.copyWith( 448 - fontWeight: FontWeight.bold, 449 - fontSize: 15, 450 - color: theme.colorScheme.onSurface, 451 - ), 452 - ), 453 - if (apiService.currentUser?.handle != null) 454 - Text( 455 - '@${apiService.currentUser!.handle}', 456 - style: theme.textTheme.bodySmall?.copyWith( 457 - color: theme.hintColor, 458 - fontSize: 11, 459 - ), 460 - ), 461 - const SizedBox(height: 6), 462 - Row( 463 - mainAxisAlignment: MainAxisAlignment.start, 464 - children: [ 465 - Text( 466 - (apiService.currentUser?.followersCount ?? 0).toString(), 467 - style: theme.textTheme.bodyMedium?.copyWith( 468 - fontWeight: FontWeight.bold, 469 - fontSize: 13, 470 - color: theme.colorScheme.onSurface, 471 - ), 472 - ), 473 - const SizedBox(width: 4), 474 - Text( 475 - 'Followers', 476 - style: theme.textTheme.bodySmall?.copyWith( 477 - color: theme.hintColor, 478 - fontSize: 10, 479 - ), 480 - ), 481 - const SizedBox(width: 16), 482 - Text( 483 - (apiService.currentUser?.followsCount ?? 0).toString(), 484 - style: theme.textTheme.bodyMedium?.copyWith( 485 - fontWeight: FontWeight.bold, 486 - fontSize: 13, 487 - color: theme.colorScheme.onSurface, 488 - ), 489 - ), 490 - const SizedBox(width: 4), 491 - Text( 492 - 'Following', 493 - style: theme.textTheme.bodySmall?.copyWith( 494 - color: theme.hintColor, 495 - fontSize: 10, 496 - ), 497 - ), 498 - ], 499 - ), 500 - ], 501 - ), 502 - ), 503 - ListTile( 504 - leading: const Icon(FontAwesomeIcons.house), 505 - title: const Text('Home'), 506 - onTap: () { 507 - Navigator.pop(context); 508 - setState(() { 509 - showProfile = false; 510 - showNotifications = false; 511 - showExplore = false; 512 - }); 513 - }, 514 - ), 515 - ListTile( 516 - leading: const Icon(FontAwesomeIcons.magnifyingGlass), 517 - title: const Text('Explore'), 518 - onTap: () { 519 - Navigator.pop(context); 520 - setState(() { 521 - showExplore = true; 522 - showProfile = false; 523 - showNotifications = false; 524 - }); 525 - }, 526 - ), 527 - ListTile( 528 - leading: const Icon(FontAwesomeIcons.user), 529 - title: const Text('Profile'), 530 - onTap: () { 531 - Navigator.pop(context); 532 - setState(() { 533 - showProfile = true; 534 - showNotifications = false; 535 - showExplore = false; 536 - }); 537 - }, 538 - ), 539 - ListTile( 540 - leading: const Icon(FontAwesomeIcons.list), 541 - title: const Text('Logs'), 542 - onTap: () { 543 - Navigator.pop(context); 544 - Navigator.of( 545 - context, 546 - ).push(MaterialPageRoute(builder: (context) => const LogPage())); 547 - }, 548 - ), 549 - const SizedBox(height: 16), 550 - Padding( 551 - padding: const EdgeInsets.only(bottom: 16.0), 552 - child: Center(child: AppVersionText()), 553 - ), 554 - ], 555 - ), 556 - ), 557 appBar: (showExplore || showNotifications) 558 ? AppBar( 559 backgroundColor: theme.appBarTheme.backgroundColor,
··· 1 import 'package:flutter/material.dart'; 2 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 import 'package:grain/api.dart'; 5 import 'package:grain/screens/create_gallery_page.dart'; 6 import 'package:grain/widgets/app_version_text.dart'; 7 import 'package:grain/widgets/bottom_nav_bar.dart'; 8 import 'package:grain/widgets/timeline_item.dart'; 9 10 + import '../providers/gallery_cache_provider.dart'; 11 import 'explore_page.dart'; 12 import 'log_page.dart'; 13 import 'notifications_page.dart'; 14 import 'profile_page.dart'; 15 16 class MyHomePage extends StatefulWidget { 17 final String title; 18 final VoidCallback? onSignOut; ··· 26 bool showProfile = false; 27 bool showNotifications = false; 28 bool showExplore = false; 29 + List<String> _timelineUris = []; 30 + List<String> _followingTimelineUris = []; 31 bool _timelineLoading = true; 32 bool _followingTimelineLoading = false; 33 int _tabIndex = 0; // 0 = Timeline, 1 = Following ··· 41 } 42 43 Future<void> _fetchTimeline({String? algorithm}) async { 44 + final container = ProviderScope.containerOf(context, listen: false); 45 if (algorithm == "following") { 46 setState(() { 47 _followingTimelineLoading = true; 48 }); 49 try { 50 final galleries = await apiService.getTimeline(algorithm: algorithm); 51 + print("Fetched following timeline: ${galleries.length} items"); 52 + container.read(galleryCacheProvider.notifier).setGalleries(galleries); 53 setState(() { 54 + _followingTimelineUris = galleries.map((g) => g.uri).toList(); 55 _followingTimelineLoading = false; 56 }); 57 } catch (e) { 58 setState(() { 59 + _followingTimelineUris = []; 60 _followingTimelineLoading = false; 61 }); 62 } ··· 66 }); 67 try { 68 final galleries = await apiService.getTimeline(algorithm: algorithm); 69 + container.read(galleryCacheProvider.notifier).setGalleries(galleries); 70 setState(() { 71 + _timelineUris = galleries.map((g) => g.uri).toList(); 72 _timelineLoading = false; 73 }); 74 } catch (e) { 75 setState(() { 76 + _timelineUris = []; 77 _timelineLoading = false; 78 }); 79 } ··· 124 125 Widget _buildTimelineSliver(BuildContext context, {bool following = false}) { 126 final loading = following ? _followingTimelineLoading : _timelineLoading; 127 + final uris = following ? _followingTimelineUris : _timelineUris; 128 return CustomScrollView( 129 key: PageStorageKey(following ? 'following' : 'timeline'), 130 slivers: [ 131 SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)), 132 + if (uris.isEmpty && loading) 133 SliverFillRemaining( 134 hasScrollBody: false, 135 child: Center( ··· 139 ), 140 ), 141 ), 142 + if (uris.isEmpty && !loading) 143 SliverFillRemaining( 144 hasScrollBody: false, 145 child: Center( 146 child: Text(following ? 'No following timeline items.' : 'No timeline items.'), 147 ), 148 ), 149 + if (uris.isNotEmpty) 150 SliverList( 151 delegate: SliverChildBuilderDelegate((context, index) { 152 + final uri = uris[index]; 153 + return TimelineItemWidget(galleryUri: uri); 154 + }, childCount: uris.length), 155 ), 156 ], 157 ); 158 } 159 160 + Widget _buildAppDrawer(ThemeData theme, String? avatarUrl) { 161 + return Drawer( 162 + child: ListView( 163 + padding: EdgeInsets.zero, 164 + children: [ 165 + Container( 166 + height: 250, 167 + decoration: BoxDecoration( 168 + color: theme.colorScheme.surface, 169 + border: Border(bottom: BorderSide(color: theme.dividerColor, width: 1)), 170 + ), 171 + padding: const EdgeInsets.fromLTRB(16, 115, 16, 16), 172 + child: Column( 173 + crossAxisAlignment: CrossAxisAlignment.start, 174 + mainAxisAlignment: MainAxisAlignment.center, 175 + children: [ 176 + CircleAvatar( 177 + radius: 22, 178 + backgroundColor: theme.scaffoldBackgroundColor, 179 + backgroundImage: (avatarUrl != null && avatarUrl.isNotEmpty) 180 + ? NetworkImage(avatarUrl) 181 + : null, 182 + child: (avatarUrl == null || avatarUrl.isEmpty) 183 + ? Icon(Icons.person, size: 44, color: theme.hintColor) 184 + : null, 185 + ), 186 + const SizedBox(height: 6), 187 + Text( 188 + apiService.currentUser?.displayName ?? '', 189 + style: theme.textTheme.bodyLarge?.copyWith( 190 + fontWeight: FontWeight.bold, 191 + color: theme.colorScheme.onSurface, 192 + ), 193 ), 194 + if (apiService.currentUser?.handle != null) 195 + Text( 196 + '@${apiService.currentUser!.handle}', 197 + style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), 198 + ), 199 + const SizedBox(height: 6), 200 + Row( 201 + mainAxisAlignment: MainAxisAlignment.start, 202 children: [ 203 + Text( 204 + (apiService.currentUser?.followersCount ?? 0).toString(), 205 + style: theme.textTheme.bodyMedium?.copyWith( 206 + fontWeight: FontWeight.bold, 207 + color: theme.colorScheme.onSurface, 208 ), 209 ), 210 + const SizedBox(width: 4), 211 Text( 212 + 'Followers', 213 + style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), 214 + ), 215 + const SizedBox(width: 16), 216 + Text( 217 + (apiService.currentUser?.followsCount ?? 0).toString(), 218 + style: theme.textTheme.bodyMedium?.copyWith( 219 fontWeight: FontWeight.bold, 220 color: theme.colorScheme.onSurface, 221 ), 222 ), 223 + const SizedBox(width: 4), 224 + Text( 225 + 'Following', 226 + style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), 227 ), 228 ], 229 ), 230 + ], 231 + ), 232 + ), 233 + ListTile( 234 + leading: const Icon(FontAwesomeIcons.house), 235 + title: const Text('Home'), 236 + onTap: () { 237 + Navigator.pop(context); 238 + setState(() { 239 + showProfile = false; 240 + showNotifications = false; 241 + showExplore = false; 242 + }); 243 + }, 244 + ), 245 + ListTile( 246 + leading: const Icon(FontAwesomeIcons.magnifyingGlass), 247 + title: const Text('Explore'), 248 + onTap: () { 249 + Navigator.pop(context); 250 + setState(() { 251 + showExplore = true; 252 + showProfile = false; 253 + showNotifications = false; 254 + }); 255 + }, 256 + ), 257 + ListTile( 258 + leading: const Icon(FontAwesomeIcons.user), 259 + title: const Text('Profile'), 260 + onTap: () { 261 + Navigator.pop(context); 262 + setState(() { 263 + showProfile = true; 264 + showNotifications = false; 265 + showExplore = false; 266 + }); 267 + }, 268 + ), 269 + ListTile( 270 + leading: const Icon(FontAwesomeIcons.list), 271 + title: const Text('Logs'), 272 + onTap: () { 273 + Navigator.pop(context); 274 + Navigator.of(context).push(MaterialPageRoute(builder: (context) => const LogPage())); 275 + }, 276 ), 277 + const SizedBox(height: 16), 278 + Padding( 279 + padding: const EdgeInsets.only(bottom: 16.0), 280 + child: Center(child: AppVersionText()), 281 + ), 282 + ], 283 + ), 284 + ); 285 + } 286 + 287 + @override 288 + Widget build(BuildContext context) { 289 + final theme = Theme.of(context); 290 + final avatarUrl = apiService.currentUser?.avatar; 291 + if (apiService.currentUser == null) { 292 + return Scaffold( 293 + body: Center( 294 + child: CircularProgressIndicator(strokeWidth: 2, color: theme.colorScheme.primary), 295 ), 296 + ); 297 + } 298 + // Home page: show tabs 299 + if (!showProfile && !showNotifications && !showExplore) { 300 + if (_tabController == null) _initTabController(); 301 + return Scaffold( 302 + onDrawerChanged: (isOpen) { 303 + setState(() {}); 304 + }, 305 + drawer: _buildAppDrawer(theme, avatarUrl), 306 body: NestedScrollView( 307 floatHeaderSlivers: true, 308 headerSliverBuilder: (context, innerBoxIsScrolled) => [ ··· 415 } 416 // Explore, Notifications, Profile: no tabs, no TabController 417 return Scaffold( 418 + drawer: _buildAppDrawer(theme, avatarUrl), 419 appBar: (showExplore || showNotifications) 420 ? AppBar( 421 backgroundColor: theme.appBarTheme.backgroundColor,
+3 -3
lib/screens/notifications_page.dart
··· 77 return ListTile( 78 leading: CircleAvatar( 79 backgroundColor: theme.colorScheme.surfaceVariant, 80 - backgroundImage: author.avatar.isNotEmpty ? NetworkImage(author.avatar) : null, 81 - child: author.avatar.isEmpty 82 ? Icon(Icons.account_circle, color: theme.iconTheme.color) 83 : null, 84 ), 85 title: Text( 86 - author.displayName.isNotEmpty ? author.displayName : '@${author.handle}', 87 style: theme.textTheme.bodyLarge, 88 ), 89 subtitle: Text(
··· 77 return ListTile( 78 leading: CircleAvatar( 79 backgroundColor: theme.colorScheme.surfaceVariant, 80 + backgroundImage: (author.avatar?.isNotEmpty ?? false) ? NetworkImage(author.avatar!) : null, 81 + child: (author.avatar?.isEmpty ?? true) 82 ? Icon(Icons.account_circle, color: theme.iconTheme.color) 83 : null, 84 ), 85 title: Text( 86 + (author.displayName?.isNotEmpty ?? false) ? author.displayName! : '@${author.handle}', 87 style: theme.textTheme.bodyLarge, 88 ), 89 subtitle: Text(
+7 -5
lib/screens/profile_page.dart
··· 108 if ((profile?.description ?? '').isNotEmpty) { 109 try { 110 final desc = profile != null ? profile.description : ''; 111 - descriptionFacets = await _extractFacets(desc); 112 } catch (_) { 113 descriptionFacets = null; 114 } ··· 325 if (_galleries.isNotEmpty && index < _galleries.length) { 326 final gallery = _galleries[index]; 327 final hasPhoto = 328 - gallery.items.isNotEmpty && gallery.items[0].thumb.isNotEmpty; 329 return GestureDetector( 330 onTap: () { 331 if (gallery.uri.isNotEmpty) { ··· 348 ? AppImage(url: gallery.items[0].thumb, fit: BoxFit.cover) 349 : Center( 350 child: Text( 351 - gallery.title, 352 style: TextStyle( 353 fontSize: 12, 354 color: theme.colorScheme.onSurfaceVariant, ··· 402 itemBuilder: (context, index) { 403 final gallery = _favs[index]; 404 final hasPhoto = 405 - gallery.items.isNotEmpty && gallery.items[0].thumb.isNotEmpty; 406 return GestureDetector( 407 onTap: () { 408 if (gallery.uri.isNotEmpty) { ··· 425 ? AppImage(url: gallery.items[0].thumb, fit: BoxFit.cover) 426 : Center( 427 child: Text( 428 - gallery.title, 429 style: TextStyle( 430 fontSize: 12, 431 color: theme.colorScheme.onSurfaceVariant,
··· 108 if ((profile?.description ?? '').isNotEmpty) { 109 try { 110 final desc = profile != null ? profile.description : ''; 111 + descriptionFacets = await _extractFacets(desc ?? ''); 112 } catch (_) { 113 descriptionFacets = null; 114 } ··· 325 if (_galleries.isNotEmpty && index < _galleries.length) { 326 final gallery = _galleries[index]; 327 final hasPhoto = 328 + gallery.items.isNotEmpty && 329 + (gallery.items[0].thumb?.isNotEmpty ?? false); 330 return GestureDetector( 331 onTap: () { 332 if (gallery.uri.isNotEmpty) { ··· 349 ? AppImage(url: gallery.items[0].thumb, fit: BoxFit.cover) 350 : Center( 351 child: Text( 352 + gallery.title ?? '', 353 style: TextStyle( 354 fontSize: 12, 355 color: theme.colorScheme.onSurfaceVariant, ··· 403 itemBuilder: (context, index) { 404 final gallery = _favs[index]; 405 final hasPhoto = 406 + gallery.items.isNotEmpty && 407 + (gallery.items[0].thumb?.isNotEmpty ?? false); 408 return GestureDetector( 409 onTap: () { 410 if (gallery.uri.isNotEmpty) { ··· 427 ? AppImage(url: gallery.items[0].thumb, fit: BoxFit.cover) 428 : Center( 429 child: Text( 430 + gallery.title ?? '', 431 style: TextStyle( 432 fontSize: 12, 433 color: theme.colorScheme.onSurfaceVariant,
+10 -4
lib/widgets/gallery_action_buttons.dart
··· 1 import 'package:flutter/material.dart'; 2 import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 import 'package:grain/app_theme.dart'; 4 import 'package:grain/models/gallery.dart'; 5 import 'package:share_plus/share_plus.dart'; 6 7 import '../screens/comments_page.dart'; 8 9 - class GalleryActionButtons extends StatelessWidget { 10 final Gallery gallery; 11 final String? currentUserDid; 12 final BuildContext parentContext; ··· 27 }); 28 29 @override 30 - Widget build(BuildContext context) { 31 final theme = Theme.of(context); 32 - final isFav = gallery.viewer != null && gallery.viewer!['fav'] != null; 33 return isLoggedIn 34 ? Row( 35 mainAxisAlignment: MainAxisAlignment.start, 36 children: [ 37 InkWell( 38 borderRadius: BorderRadius.circular(10), 39 - onTap: onFavorite ?? () {}, 40 child: Padding( 41 padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), 42 child: Row(
··· 1 import 'package:flutter/material.dart'; 2 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 4 import 'package:grain/app_theme.dart'; 5 import 'package:grain/models/gallery.dart'; 6 + import 'package:grain/providers/gallery_cache_provider.dart'; 7 import 'package:share_plus/share_plus.dart'; 8 9 import '../screens/comments_page.dart'; 10 11 + class GalleryActionButtons extends ConsumerWidget { 12 final Gallery gallery; 13 final String? currentUserDid; 14 final BuildContext parentContext; ··· 29 }); 30 31 @override 32 + Widget build(BuildContext context, WidgetRef ref) { 33 final theme = Theme.of(context); 34 + final isFav = gallery.viewer != null && gallery.viewer?.fav != null; 35 return isLoggedIn 36 ? Row( 37 mainAxisAlignment: MainAxisAlignment.start, 38 children: [ 39 InkWell( 40 borderRadius: BorderRadius.circular(10), 41 + onTap: 42 + onFavorite ?? 43 + () async { 44 + await ref.read(galleryCacheProvider.notifier).toggleFavorite(gallery.uri); 45 + }, 46 child: Padding( 47 padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), 48 child: Row(
+3 -3
lib/widgets/gallery_photo_view.dart
··· 1 import 'package:flutter/material.dart'; 2 - import 'package:grain/models/gallery.dart'; 3 import 'package:grain/widgets/app_image.dart'; 4 5 class GalleryPhotoView extends StatefulWidget { ··· 71 ), 72 ), 73 ), 74 - if (photo.alt.isNotEmpty) 75 Container( 76 width: double.infinity, 77 color: Colors.black.withOpacity(0.7), 78 padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), 79 child: Text( 80 - photo.alt, 81 style: const TextStyle(color: Colors.white, fontSize: 16), 82 textAlign: TextAlign.center, 83 ),
··· 1 import 'package:flutter/material.dart'; 2 + import 'package:grain/models/gallery_photo.dart'; 3 import 'package:grain/widgets/app_image.dart'; 4 5 class GalleryPhotoView extends StatefulWidget { ··· 71 ), 72 ), 73 ), 74 + if (photo.alt != null && photo.alt?.isNotEmpty == true) 75 Container( 76 width: double.infinity, 77 color: Colors.black.withOpacity(0.7), 78 padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), 79 child: Text( 80 + photo.alt!, 81 style: const TextStyle(color: Colors.white, fontSize: 16), 82 textAlign: TextAlign.center, 83 ),
+3 -1
lib/widgets/gallery_preview.dart
··· 13 final Color bgColor = theme.brightness == Brightness.dark 14 ? Colors.grey[900]! 15 : Colors.grey[100]!; 16 - final photos = gallery.items.where((item) => item.thumb.isNotEmpty).toList(); 17 return AspectRatio( 18 aspectRatio: 3 / 2, 19 child: Row(
··· 13 final Color bgColor = theme.brightness == Brightness.dark 14 ? Colors.grey[900]! 15 : Colors.grey[100]!; 16 + final photos = gallery.items 17 + .where((item) => item.thumb != null && item.thumb!.isNotEmpty) 18 + .toList(); 19 return AspectRatio( 20 aspectRatio: 3 / 2, 21 child: Row(
+17 -12
lib/widgets/timeline_item.dart
··· 1 import 'package:flutter/material.dart'; 2 import 'package:grain/api.dart'; 3 - import 'package:grain/models/gallery.dart'; 4 import 'package:grain/utils.dart'; 5 import 'package:grain/widgets/app_image.dart'; 6 import 'package:grain/widgets/gallery_action_buttons.dart'; 7 import 'package:grain/widgets/gallery_preview.dart'; 8 9 import '../screens/gallery_page.dart'; 10 import '../screens/profile_page.dart'; 11 12 - class TimelineItemWidget extends StatelessWidget { 13 - final Gallery gallery; 14 final VoidCallback? onProfileTap; 15 - const TimelineItemWidget({super.key, required this.gallery, this.onProfileTap}); 16 17 @override 18 - Widget build(BuildContext context) { 19 final actor = gallery.creator; 20 final createdAt = gallery.createdAt; 21 final theme = Theme.of(context); ··· 42 child: CircleAvatar( 43 radius: 18, 44 backgroundColor: theme.scaffoldBackgroundColor, 45 - child: (actor != null && actor.avatar.isNotEmpty) 46 ? ClipOval( 47 child: AppImage( 48 url: actor.avatar, ··· 64 children: [ 65 Flexible( 66 child: Text( 67 - actor != null && actor.displayName.isNotEmpty 68 - ? actor.displayName 69 : (actor != null ? '@${actor.handle}' : ''), 70 style: theme.textTheme.titleMedium?.copyWith( 71 fontWeight: FontWeight.w600, ··· 117 ) 118 else 119 const SizedBox.shrink(), 120 - if (gallery.title.isNotEmpty) 121 Padding( 122 padding: const EdgeInsets.only(top: 8, left: 8, right: 8), 123 child: Text( 124 - gallery.title, 125 style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600), 126 ), 127 ), 128 - if (gallery.description.isNotEmpty) 129 Padding( 130 padding: const EdgeInsets.only(top: 4, left: 8, right: 8), 131 child: Text( 132 - gallery.description, 133 style: theme.textTheme.bodySmall?.copyWith( 134 fontSize: 13, 135 color: theme.colorScheme.onSurfaceVariant,
··· 1 import 'package:flutter/material.dart'; 2 + import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 import 'package:grain/api.dart'; 4 import 'package:grain/utils.dart'; 5 import 'package:grain/widgets/app_image.dart'; 6 import 'package:grain/widgets/gallery_action_buttons.dart'; 7 import 'package:grain/widgets/gallery_preview.dart'; 8 9 + import '../providers/gallery_cache_provider.dart'; 10 import '../screens/gallery_page.dart'; 11 import '../screens/profile_page.dart'; 12 13 + class TimelineItemWidget extends ConsumerWidget { 14 + final String galleryUri; 15 final VoidCallback? onProfileTap; 16 + const TimelineItemWidget({super.key, required this.galleryUri, this.onProfileTap}); 17 18 @override 19 + Widget build(BuildContext context, WidgetRef ref) { 20 + final gallery = ref.watch(galleryCacheProvider)[galleryUri]; 21 + if (gallery == null) { 22 + return const SizedBox.shrink(); // or a loading/placeholder widget 23 + } 24 final actor = gallery.creator; 25 final createdAt = gallery.createdAt; 26 final theme = Theme.of(context); ··· 47 child: CircleAvatar( 48 radius: 18, 49 backgroundColor: theme.scaffoldBackgroundColor, 50 + child: (actor != null && (actor.avatar?.isNotEmpty ?? false)) 51 ? ClipOval( 52 child: AppImage( 53 url: actor.avatar, ··· 69 children: [ 70 Flexible( 71 child: Text( 72 + actor != null && (actor.displayName?.isNotEmpty ?? false) 73 + ? actor.displayName ?? '' 74 : (actor != null ? '@${actor.handle}' : ''), 75 style: theme.textTheme.titleMedium?.copyWith( 76 fontWeight: FontWeight.w600, ··· 122 ) 123 else 124 const SizedBox.shrink(), 125 + if (gallery.title?.isNotEmpty == true) 126 Padding( 127 padding: const EdgeInsets.only(top: 8, left: 8, right: 8), 128 child: Text( 129 + gallery.title ?? '', 130 style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600), 131 ), 132 ), 133 + if (gallery.description?.isNotEmpty == true) 134 Padding( 135 padding: const EdgeInsets.only(top: 4, left: 8, right: 8), 136 child: Text( 137 + gallery.description ?? '', 138 style: theme.textTheme.bodySmall?.copyWith( 139 fontSize: 13, 140 color: theme.colorScheme.onSurfaceVariant,
+395 -3
pubspec.lock
··· 1 # Generated by pub 2 # See https://dart.dev/tools/pub/glossary#lockfile 3 packages: 4 archive: 5 dependency: transitive 6 description: ··· 9 url: "https://pub.dev" 10 source: hosted 11 version: "4.0.7" 12 asn1lib: 13 dependency: transitive 14 description: ··· 57 url: "https://pub.dev" 58 source: hosted 59 version: "2.1.2" 60 cached_network_image: 61 dependency: "direct main" 62 description: ··· 89 url: "https://pub.dev" 90 source: hosted 91 version: "1.4.0" 92 clock: 93 dependency: transitive 94 description: ··· 97 url: "https://pub.dev" 98 source: hosted 99 version: "1.1.2" 100 collection: 101 dependency: transitive 102 description: ··· 145 url: "https://pub.dev" 146 source: hosted 147 version: "1.0.8" 148 desktop_webview_window: 149 dependency: transitive 150 description: ··· 254 url: "https://pub.dev" 255 source: hosted 256 version: "2.0.28" 257 flutter_secure_storage: 258 dependency: "direct main" 259 description: ··· 336 url: "https://pub.dev" 337 source: hosted 338 version: "10.8.0" 339 freezed_annotation: 340 - dependency: transitive 341 description: 342 name: freezed_annotation 343 sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 344 url: "https://pub.dev" 345 source: hosted 346 version: "2.4.4" 347 google_fonts: 348 dependency: "direct main" 349 description: ··· 352 url: "https://pub.dev" 353 source: hosted 354 version: "6.2.1" 355 http: 356 dependency: "direct main" 357 description: ··· 360 url: "https://pub.dev" 361 source: hosted 362 version: "1.4.0" 363 http_parser: 364 dependency: transitive 365 description: ··· 440 url: "https://pub.dev" 441 source: hosted 442 version: "0.2.1+1" 443 jose: 444 dependency: "direct main" 445 description: ··· 457 source: hosted 458 version: "0.6.7" 459 json_annotation: 460 - dependency: transitive 461 description: 462 name: json_annotation 463 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 464 url: "https://pub.dev" 465 source: hosted 466 version: "4.9.0" 467 leak_tracker: 468 dependency: transitive 469 description: ··· 504 url: "https://pub.dev" 505 source: hosted 506 version: "2.6.0" 507 matcher: 508 dependency: transitive 509 description: ··· 552 url: "https://pub.dev" 553 source: hosted 554 version: "2.1.0" 555 package_info_plus: 556 dependency: "direct main" 557 description: ··· 656 url: "https://pub.dev" 657 source: hosted 658 version: "3.9.1" 659 posix: 660 dependency: transitive 661 description: ··· 664 url: "https://pub.dev" 665 source: hosted 666 version: "6.0.3" 667 quiver: 668 dependency: transitive 669 description: ··· 672 url: "https://pub.dev" 673 source: hosted 674 version: "3.2.2" 675 rxdart: 676 dependency: transitive 677 description: ··· 696 url: "https://pub.dev" 697 source: hosted 698 version: "6.0.0" 699 sky_engine: 700 dependency: transitive 701 description: flutter 702 source: sdk 703 version: "0.0.0" 704 source_span: 705 dependency: transitive 706 description: ··· 765 url: "https://pub.dev" 766 source: hosted 767 version: "1.12.1" 768 stream_channel: 769 dependency: transitive 770 description: ··· 773 url: "https://pub.dev" 774 source: hosted 775 version: "2.1.4" 776 string_scanner: 777 dependency: transitive 778 description: ··· 805 url: "https://pub.dev" 806 source: hosted 807 version: "0.7.4" 808 typed_data: 809 dependency: transitive 810 description: ··· 901 url: "https://pub.dev" 902 source: hosted 903 version: "15.0.0" 904 web: 905 dependency: transitive 906 description: ··· 966 source: hosted 967 version: "6.5.0" 968 xrpc: 969 - dependency: "direct main" 970 description: 971 name: xrpc 972 sha256: bacfa0f6824fdeaa631aad1a5fd064c3f140c771fed94cbd04df3b7d1e008709 973 url: "https://pub.dev" 974 source: hosted 975 version: "0.6.1" 976 sdks: 977 dart: ">=3.8.1 <4.0.0" 978 flutter: ">=3.27.0"
··· 1 # Generated by pub 2 # See https://dart.dev/tools/pub/glossary#lockfile 3 packages: 4 + _fe_analyzer_shared: 5 + dependency: transitive 6 + description: 7 + name: _fe_analyzer_shared 8 + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 9 + url: "https://pub.dev" 10 + source: hosted 11 + version: "80.0.0" 12 + analyzer: 13 + dependency: "direct overridden" 14 + description: 15 + name: analyzer 16 + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" 17 + url: "https://pub.dev" 18 + source: hosted 19 + version: "7.3.0" 20 + analyzer_plugin: 21 + dependency: "direct overridden" 22 + description: 23 + name: analyzer_plugin 24 + sha256: "1d460d14e3c2ae36dc2b32cef847c4479198cf87704f63c3c3c8150ee50c3916" 25 + url: "https://pub.dev" 26 + source: hosted 27 + version: "0.12.0" 28 archive: 29 dependency: transitive 30 description: ··· 33 url: "https://pub.dev" 34 source: hosted 35 version: "4.0.7" 36 + args: 37 + dependency: transitive 38 + description: 39 + name: args 40 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 41 + url: "https://pub.dev" 42 + source: hosted 43 + version: "2.7.0" 44 asn1lib: 45 dependency: transitive 46 description: ··· 89 url: "https://pub.dev" 90 source: hosted 91 version: "2.1.2" 92 + build: 93 + dependency: transitive 94 + description: 95 + name: build 96 + sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" 97 + url: "https://pub.dev" 98 + source: hosted 99 + version: "2.5.4" 100 + build_config: 101 + dependency: transitive 102 + description: 103 + name: build_config 104 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" 105 + url: "https://pub.dev" 106 + source: hosted 107 + version: "1.1.2" 108 + build_daemon: 109 + dependency: transitive 110 + description: 111 + name: build_daemon 112 + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" 113 + url: "https://pub.dev" 114 + source: hosted 115 + version: "4.0.4" 116 + build_resolvers: 117 + dependency: transitive 118 + description: 119 + name: build_resolvers 120 + sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 121 + url: "https://pub.dev" 122 + source: hosted 123 + version: "2.5.4" 124 + build_runner: 125 + dependency: "direct dev" 126 + description: 127 + name: build_runner 128 + sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" 129 + url: "https://pub.dev" 130 + source: hosted 131 + version: "2.5.4" 132 + build_runner_core: 133 + dependency: transitive 134 + description: 135 + name: build_runner_core 136 + sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" 137 + url: "https://pub.dev" 138 + source: hosted 139 + version: "9.1.2" 140 + built_collection: 141 + dependency: transitive 142 + description: 143 + name: built_collection 144 + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 145 + url: "https://pub.dev" 146 + source: hosted 147 + version: "5.1.1" 148 + built_value: 149 + dependency: transitive 150 + description: 151 + name: built_value 152 + sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" 153 + url: "https://pub.dev" 154 + source: hosted 155 + version: "8.10.1" 156 cached_network_image: 157 dependency: "direct main" 158 description: ··· 185 url: "https://pub.dev" 186 source: hosted 187 version: "1.4.0" 188 + checked_yaml: 189 + dependency: transitive 190 + description: 191 + name: checked_yaml 192 + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" 193 + url: "https://pub.dev" 194 + source: hosted 195 + version: "2.0.4" 196 + ci: 197 + dependency: transitive 198 + description: 199 + name: ci 200 + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" 201 + url: "https://pub.dev" 202 + source: hosted 203 + version: "0.1.0" 204 + cli_util: 205 + dependency: transitive 206 + description: 207 + name: cli_util 208 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c 209 + url: "https://pub.dev" 210 + source: hosted 211 + version: "0.4.2" 212 clock: 213 dependency: transitive 214 description: ··· 217 url: "https://pub.dev" 218 source: hosted 219 version: "1.1.2" 220 + code_builder: 221 + dependency: transitive 222 + description: 223 + name: code_builder 224 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" 225 + url: "https://pub.dev" 226 + source: hosted 227 + version: "4.10.1" 228 collection: 229 dependency: transitive 230 description: ··· 273 url: "https://pub.dev" 274 source: hosted 275 version: "1.0.8" 276 + custom_lint: 277 + dependency: "direct dev" 278 + description: 279 + name: custom_lint 280 + sha256: "021897cce2b6c783b2521543e362e7fe1a2eaab17bf80514d8de37f99942ed9e" 281 + url: "https://pub.dev" 282 + source: hosted 283 + version: "0.7.3" 284 + custom_lint_builder: 285 + dependency: transitive 286 + description: 287 + name: custom_lint_builder 288 + sha256: e4235b9d8cef59afe621eba086d245205c8a0a6c70cd470be7cb17494d6df32d 289 + url: "https://pub.dev" 290 + source: hosted 291 + version: "0.7.3" 292 + custom_lint_core: 293 + dependency: transitive 294 + description: 295 + name: custom_lint_core 296 + sha256: "6dcee8a017181941c51a110da7e267c1d104dc74bec8862eeb8c85b5c8759a9e" 297 + url: "https://pub.dev" 298 + source: hosted 299 + version: "0.7.1" 300 + custom_lint_visitor: 301 + dependency: "direct overridden" 302 + description: 303 + name: custom_lint_visitor 304 + sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" 305 + url: "https://pub.dev" 306 + source: hosted 307 + version: "1.0.0+7.3.0" 308 + dart_style: 309 + dependency: transitive 310 + description: 311 + name: dart_style 312 + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" 313 + url: "https://pub.dev" 314 + source: hosted 315 + version: "3.1.0" 316 desktop_webview_window: 317 dependency: transitive 318 description: ··· 422 url: "https://pub.dev" 423 source: hosted 424 version: "2.0.28" 425 + flutter_riverpod: 426 + dependency: "direct main" 427 + description: 428 + name: flutter_riverpod 429 + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" 430 + url: "https://pub.dev" 431 + source: hosted 432 + version: "2.6.1" 433 flutter_secure_storage: 434 dependency: "direct main" 435 description: ··· 512 url: "https://pub.dev" 513 source: hosted 514 version: "10.8.0" 515 + freezed: 516 + dependency: "direct dev" 517 + description: 518 + name: freezed 519 + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" 520 + url: "https://pub.dev" 521 + source: hosted 522 + version: "2.5.8" 523 freezed_annotation: 524 + dependency: "direct main" 525 description: 526 name: freezed_annotation 527 sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 528 url: "https://pub.dev" 529 source: hosted 530 version: "2.4.4" 531 + frontend_server_client: 532 + dependency: transitive 533 + description: 534 + name: frontend_server_client 535 + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 536 + url: "https://pub.dev" 537 + source: hosted 538 + version: "4.0.0" 539 + glob: 540 + dependency: transitive 541 + description: 542 + name: glob 543 + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de 544 + url: "https://pub.dev" 545 + source: hosted 546 + version: "2.1.3" 547 google_fonts: 548 dependency: "direct main" 549 description: ··· 552 url: "https://pub.dev" 553 source: hosted 554 version: "6.2.1" 555 + graphs: 556 + dependency: transitive 557 + description: 558 + name: graphs 559 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" 560 + url: "https://pub.dev" 561 + source: hosted 562 + version: "2.3.2" 563 + hotreloader: 564 + dependency: transitive 565 + description: 566 + name: hotreloader 567 + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b 568 + url: "https://pub.dev" 569 + source: hosted 570 + version: "4.3.0" 571 http: 572 dependency: "direct main" 573 description: ··· 576 url: "https://pub.dev" 577 source: hosted 578 version: "1.4.0" 579 + http_multi_server: 580 + dependency: transitive 581 + description: 582 + name: http_multi_server 583 + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 584 + url: "https://pub.dev" 585 + source: hosted 586 + version: "3.2.2" 587 http_parser: 588 dependency: transitive 589 description: ··· 664 url: "https://pub.dev" 665 source: hosted 666 version: "0.2.1+1" 667 + io: 668 + dependency: transitive 669 + description: 670 + name: io 671 + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b 672 + url: "https://pub.dev" 673 + source: hosted 674 + version: "1.0.5" 675 jose: 676 dependency: "direct main" 677 description: ··· 689 source: hosted 690 version: "0.6.7" 691 json_annotation: 692 + dependency: "direct main" 693 description: 694 name: json_annotation 695 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 696 url: "https://pub.dev" 697 source: hosted 698 version: "4.9.0" 699 + json_serializable: 700 + dependency: "direct dev" 701 + description: 702 + name: json_serializable 703 + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c 704 + url: "https://pub.dev" 705 + source: hosted 706 + version: "6.9.5" 707 leak_tracker: 708 dependency: transitive 709 description: ··· 744 url: "https://pub.dev" 745 source: hosted 746 version: "2.6.0" 747 + logging: 748 + dependency: transitive 749 + description: 750 + name: logging 751 + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 752 + url: "https://pub.dev" 753 + source: hosted 754 + version: "1.3.0" 755 matcher: 756 dependency: transitive 757 description: ··· 800 url: "https://pub.dev" 801 source: hosted 802 version: "2.1.0" 803 + package_config: 804 + dependency: transitive 805 + description: 806 + name: package_config 807 + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc 808 + url: "https://pub.dev" 809 + source: hosted 810 + version: "2.2.0" 811 package_info_plus: 812 dependency: "direct main" 813 description: ··· 912 url: "https://pub.dev" 913 source: hosted 914 version: "3.9.1" 915 + pool: 916 + dependency: transitive 917 + description: 918 + name: pool 919 + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 920 + url: "https://pub.dev" 921 + source: hosted 922 + version: "1.5.1" 923 posix: 924 dependency: transitive 925 description: ··· 928 url: "https://pub.dev" 929 source: hosted 930 version: "6.0.3" 931 + pub_semver: 932 + dependency: transitive 933 + description: 934 + name: pub_semver 935 + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" 936 + url: "https://pub.dev" 937 + source: hosted 938 + version: "2.2.0" 939 + pubspec_parse: 940 + dependency: transitive 941 + description: 942 + name: pubspec_parse 943 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" 944 + url: "https://pub.dev" 945 + source: hosted 946 + version: "1.5.0" 947 quiver: 948 dependency: transitive 949 description: ··· 952 url: "https://pub.dev" 953 source: hosted 954 version: "3.2.2" 955 + riverpod: 956 + dependency: transitive 957 + description: 958 + name: riverpod 959 + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" 960 + url: "https://pub.dev" 961 + source: hosted 962 + version: "2.6.1" 963 + riverpod_analyzer_utils: 964 + dependency: transitive 965 + description: 966 + name: riverpod_analyzer_utils 967 + sha256: "837a6dc33f490706c7f4632c516bcd10804ee4d9ccc8046124ca56388715fdf3" 968 + url: "https://pub.dev" 969 + source: hosted 970 + version: "0.5.9" 971 + riverpod_annotation: 972 + dependency: "direct main" 973 + description: 974 + name: riverpod_annotation 975 + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 976 + url: "https://pub.dev" 977 + source: hosted 978 + version: "2.6.1" 979 + riverpod_generator: 980 + dependency: "direct dev" 981 + description: 982 + name: riverpod_generator 983 + sha256: "120d3310f687f43e7011bb213b90a436f1bbc300f0e4b251a72c39bccb017a4f" 984 + url: "https://pub.dev" 985 + source: hosted 986 + version: "2.6.4" 987 + riverpod_lint: 988 + dependency: "direct dev" 989 + description: 990 + name: riverpod_lint 991 + sha256: b05408412b0f75dec954e032c855bc28349eeed2d2187f94519e1ddfdf8b3693 992 + url: "https://pub.dev" 993 + source: hosted 994 + version: "2.6.4" 995 rxdart: 996 dependency: transitive 997 description: ··· 1016 url: "https://pub.dev" 1017 source: hosted 1018 version: "6.0.0" 1019 + shelf: 1020 + dependency: transitive 1021 + description: 1022 + name: shelf 1023 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 1024 + url: "https://pub.dev" 1025 + source: hosted 1026 + version: "1.4.2" 1027 + shelf_web_socket: 1028 + dependency: transitive 1029 + description: 1030 + name: shelf_web_socket 1031 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" 1032 + url: "https://pub.dev" 1033 + source: hosted 1034 + version: "3.0.0" 1035 sky_engine: 1036 dependency: transitive 1037 description: flutter 1038 source: sdk 1039 version: "0.0.0" 1040 + source_gen: 1041 + dependency: transitive 1042 + description: 1043 + name: source_gen 1044 + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" 1045 + url: "https://pub.dev" 1046 + source: hosted 1047 + version: "2.0.0" 1048 + source_helper: 1049 + dependency: transitive 1050 + description: 1051 + name: source_helper 1052 + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" 1053 + url: "https://pub.dev" 1054 + source: hosted 1055 + version: "1.3.5" 1056 source_span: 1057 dependency: transitive 1058 description: ··· 1117 url: "https://pub.dev" 1118 source: hosted 1119 version: "1.12.1" 1120 + state_notifier: 1121 + dependency: transitive 1122 + description: 1123 + name: state_notifier 1124 + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb 1125 + url: "https://pub.dev" 1126 + source: hosted 1127 + version: "1.0.0" 1128 stream_channel: 1129 dependency: transitive 1130 description: ··· 1133 url: "https://pub.dev" 1134 source: hosted 1135 version: "2.1.4" 1136 + stream_transform: 1137 + dependency: transitive 1138 + description: 1139 + name: stream_transform 1140 + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 1141 + url: "https://pub.dev" 1142 + source: hosted 1143 + version: "2.1.1" 1144 string_scanner: 1145 dependency: transitive 1146 description: ··· 1173 url: "https://pub.dev" 1174 source: hosted 1175 version: "0.7.4" 1176 + timing: 1177 + dependency: transitive 1178 + description: 1179 + name: timing 1180 + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" 1181 + url: "https://pub.dev" 1182 + source: hosted 1183 + version: "1.0.2" 1184 typed_data: 1185 dependency: transitive 1186 description: ··· 1277 url: "https://pub.dev" 1278 source: hosted 1279 version: "15.0.0" 1280 + watcher: 1281 + dependency: transitive 1282 + description: 1283 + name: watcher 1284 + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" 1285 + url: "https://pub.dev" 1286 + source: hosted 1287 + version: "1.1.2" 1288 web: 1289 dependency: transitive 1290 description: ··· 1350 source: hosted 1351 version: "6.5.0" 1352 xrpc: 1353 + dependency: transitive 1354 description: 1355 name: xrpc 1356 sha256: bacfa0f6824fdeaa631aad1a5fd064c3f140c771fed94cbd04df3b7d1e008709 1357 url: "https://pub.dev" 1358 source: hosted 1359 version: "0.6.1" 1360 + yaml: 1361 + dependency: transitive 1362 + description: 1363 + name: yaml 1364 + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 1365 + url: "https://pub.dev" 1366 + source: hosted 1367 + version: "3.1.3" 1368 sdks: 1369 dart: ">=3.8.1 <4.0.0" 1370 flutter: ">=3.27.0"
+15 -1
pubspec.yaml
··· 51 jose: ^0.3.4 52 uuid: ^4.5.1 53 crypto: ^3.0.6 54 - xrpc: ^0.6.1 55 mime: ^1.0.6 56 image: ^4.5.4 57 bluesky_text: ^0.7.2 58 59 dev_dependencies: 60 flutter_test: ··· 66 # package. See that file for information about deactivating specific lint 67 # rules and activating additional ones. 68 flutter_lints: ^5.0.0 69 70 # For information on the generic Dart part of this file, see the 71 # following page: https://dart.dev/tools/pub/pubspec
··· 51 jose: ^0.3.4 52 uuid: ^4.5.1 53 crypto: ^3.0.6 54 mime: ^1.0.6 55 image: ^4.5.4 56 bluesky_text: ^0.7.2 57 + flutter_riverpod: ^2.6.1 58 + riverpod_annotation: ^2.6.1 59 + freezed_annotation: ^2.4.4 60 + json_annotation: ^4.9.0 61 + 62 + dependency_overrides: 63 + analyzer: 7.3.0 64 + analyzer_plugin: 0.12.0 65 + custom_lint_visitor: 1.0.0+7.3.0 66 67 dev_dependencies: 68 flutter_test: ··· 74 # package. See that file for information about deactivating specific lint 75 # rules and activating additional ones. 76 flutter_lints: ^5.0.0 77 + custom_lint: ^0.7.3 78 + riverpod_lint: ^2.6.4 79 + riverpod_generator: ^2.6.4 80 + build_runner: ^2.5.4 81 + freezed: ^2.4.7 82 + json_serializable: ^6.8.0 83 84 # For information on the generic Dart part of this file, see the 85 # following page: https://dart.dev/tools/pub/pubspec