Compare commits
	
		
			10 Commits
		
	
	
		
			666a2dfbf5
			...
			3.0.0+110
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 007acedf29 | |||
| 8e903ec6c1 | |||
| b55e56c3c4 | |||
| 6f9de431b1 | |||
| a8efd26262 | |||
| e367fc3f5c | |||
| 8a1af120ea | |||
| f03f0181f8 | |||
| 6c7d42c31a | |||
| d6c829c26a | 
@@ -675,5 +675,8 @@
 | 
				
			|||||||
  "publisherFeatureDevelop": "Developer Program",
 | 
					  "publisherFeatureDevelop": "Developer Program",
 | 
				
			||||||
  "publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.",
 | 
					  "publisherFeatureDevelopDescription": "Unlock development abilities for your publisher, including custom apps, API keys, and more.",
 | 
				
			||||||
  "publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.",
 | 
					  "publisherFeatureDevelopHint": "Currently, this feature is under active development, you need send a request to unlock this feature.",
 | 
				
			||||||
  "learnMore": "Learn More"
 | 
					  "learnMore": "Learn More",
 | 
				
			||||||
 | 
					  "discoverWebArticles": "Articles from external sites",
 | 
				
			||||||
 | 
					  "webArticlesStand": "Article Stand",
 | 
				
			||||||
 | 
					  "about": "About"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,31 +40,31 @@ PODS:
 | 
				
			|||||||
  - file_picker (0.0.1):
 | 
					  - file_picker (0.0.1):
 | 
				
			||||||
    - DKImagePickerController/PhotoGallery
 | 
					    - DKImagePickerController/PhotoGallery
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
  - Firebase/CoreOnly (11.13.0):
 | 
					  - Firebase/CoreOnly (11.15.0):
 | 
				
			||||||
    - FirebaseCore (~> 11.13.0)
 | 
					    - FirebaseCore (~> 11.15.0)
 | 
				
			||||||
  - Firebase/Messaging (11.13.0):
 | 
					  - Firebase/Messaging (11.15.0):
 | 
				
			||||||
    - Firebase/CoreOnly
 | 
					    - Firebase/CoreOnly
 | 
				
			||||||
    - FirebaseMessaging (~> 11.13.0)
 | 
					    - FirebaseMessaging (~> 11.15.0)
 | 
				
			||||||
  - firebase_core (3.14.0):
 | 
					  - firebase_core (3.15.0):
 | 
				
			||||||
    - Firebase/CoreOnly (= 11.13.0)
 | 
					    - Firebase/CoreOnly (= 11.15.0)
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
  - firebase_messaging (15.2.7):
 | 
					  - firebase_messaging (15.2.8):
 | 
				
			||||||
    - Firebase/Messaging (= 11.13.0)
 | 
					    - Firebase/Messaging (= 11.15.0)
 | 
				
			||||||
    - firebase_core
 | 
					    - firebase_core
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
  - FirebaseCore (11.13.0):
 | 
					  - FirebaseCore (11.15.0):
 | 
				
			||||||
    - FirebaseCoreInternal (~> 11.13.0)
 | 
					    - FirebaseCoreInternal (~> 11.15.0)
 | 
				
			||||||
    - GoogleUtilities/Environment (~> 8.1)
 | 
					    - GoogleUtilities/Environment (~> 8.1)
 | 
				
			||||||
    - GoogleUtilities/Logger (~> 8.1)
 | 
					    - GoogleUtilities/Logger (~> 8.1)
 | 
				
			||||||
  - FirebaseCoreInternal (11.13.0):
 | 
					  - FirebaseCoreInternal (11.15.0):
 | 
				
			||||||
    - "GoogleUtilities/NSData+zlib (~> 8.1)"
 | 
					    - "GoogleUtilities/NSData+zlib (~> 8.1)"
 | 
				
			||||||
  - FirebaseInstallations (11.13.0):
 | 
					  - FirebaseInstallations (11.15.0):
 | 
				
			||||||
    - FirebaseCore (~> 11.13.0)
 | 
					    - FirebaseCore (~> 11.15.0)
 | 
				
			||||||
    - GoogleUtilities/Environment (~> 8.1)
 | 
					    - GoogleUtilities/Environment (~> 8.1)
 | 
				
			||||||
    - GoogleUtilities/UserDefaults (~> 8.1)
 | 
					    - GoogleUtilities/UserDefaults (~> 8.1)
 | 
				
			||||||
    - PromisesObjC (~> 2.4)
 | 
					    - PromisesObjC (~> 2.4)
 | 
				
			||||||
  - FirebaseMessaging (11.13.0):
 | 
					  - FirebaseMessaging (11.15.0):
 | 
				
			||||||
    - FirebaseCore (~> 11.13.0)
 | 
					    - FirebaseCore (~> 11.15.0)
 | 
				
			||||||
    - FirebaseInstallations (~> 11.0)
 | 
					    - FirebaseInstallations (~> 11.0)
 | 
				
			||||||
    - GoogleDataTransport (~> 10.0)
 | 
					    - GoogleDataTransport (~> 10.0)
 | 
				
			||||||
    - GoogleUtilities/AppDelegateSwizzler (~> 8.1)
 | 
					    - GoogleUtilities/AppDelegateSwizzler (~> 8.1)
 | 
				
			||||||
@@ -80,6 +80,8 @@ PODS:
 | 
				
			|||||||
  - flutter_inappwebview_ios/Core (0.0.1):
 | 
					  - flutter_inappwebview_ios/Core (0.0.1):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - OrderedSet (~> 6.0.3)
 | 
					    - OrderedSet (~> 6.0.3)
 | 
				
			||||||
 | 
					  - flutter_keyboard_visibility (0.0.1):
 | 
				
			||||||
 | 
					    - Flutter
 | 
				
			||||||
  - flutter_native_splash (2.4.3):
 | 
					  - flutter_native_splash (2.4.3):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
  - flutter_platform_alert (0.0.1):
 | 
					  - flutter_platform_alert (0.0.1):
 | 
				
			||||||
@@ -128,8 +130,8 @@ PODS:
 | 
				
			|||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
  - irondash_engine_context (0.0.1):
 | 
					  - irondash_engine_context (0.0.1):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
  - Kingfisher (8.3.2)
 | 
					  - Kingfisher (8.3.3)
 | 
				
			||||||
  - livekit_client (2.4.8):
 | 
					  - livekit_client (2.4.9):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - flutter_webrtc
 | 
					    - flutter_webrtc
 | 
				
			||||||
    - WebRTC-SDK (= 125.6422.07)
 | 
					    - WebRTC-SDK (= 125.6422.07)
 | 
				
			||||||
@@ -155,6 +157,8 @@ PODS:
 | 
				
			|||||||
  - path_provider_foundation (0.0.1):
 | 
					  - path_provider_foundation (0.0.1):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
 | 
					  - pointer_interceptor_ios (0.0.1):
 | 
				
			||||||
 | 
					    - Flutter
 | 
				
			||||||
  - PromisesObjC (2.4.0)
 | 
					  - PromisesObjC (2.4.0)
 | 
				
			||||||
  - receive_sharing_intent (1.8.1):
 | 
					  - receive_sharing_intent (1.8.1):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
@@ -217,6 +221,7 @@ DEPENDENCIES:
 | 
				
			|||||||
  - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
 | 
					  - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
 | 
				
			||||||
  - Flutter (from `Flutter`)
 | 
					  - Flutter (from `Flutter`)
 | 
				
			||||||
  - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
 | 
					  - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
 | 
				
			||||||
 | 
					  - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
 | 
				
			||||||
  - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
 | 
					  - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
 | 
				
			||||||
  - flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
 | 
					  - flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
 | 
				
			||||||
  - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
 | 
					  - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
 | 
				
			||||||
@@ -235,6 +240,7 @@ DEPENDENCIES:
 | 
				
			|||||||
  - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
 | 
					  - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
 | 
				
			||||||
  - pasteboard (from `.symlinks/plugins/pasteboard/ios`)
 | 
					  - pasteboard (from `.symlinks/plugins/pasteboard/ios`)
 | 
				
			||||||
  - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
 | 
					  - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
 | 
				
			||||||
 | 
					  - pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
 | 
				
			||||||
  - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
 | 
					  - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
 | 
				
			||||||
  - record_ios (from `.symlinks/plugins/record_ios/ios`)
 | 
					  - record_ios (from `.symlinks/plugins/record_ios/ios`)
 | 
				
			||||||
  - share_plus (from `.symlinks/plugins/share_plus/ios`)
 | 
					  - share_plus (from `.symlinks/plugins/share_plus/ios`)
 | 
				
			||||||
@@ -286,6 +292,8 @@ EXTERNAL SOURCES:
 | 
				
			|||||||
    :path: Flutter
 | 
					    :path: Flutter
 | 
				
			||||||
  flutter_inappwebview_ios:
 | 
					  flutter_inappwebview_ios:
 | 
				
			||||||
    :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
 | 
					    :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility:
 | 
				
			||||||
 | 
					    :path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
 | 
				
			||||||
  flutter_native_splash:
 | 
					  flutter_native_splash:
 | 
				
			||||||
    :path: ".symlinks/plugins/flutter_native_splash/ios"
 | 
					    :path: ".symlinks/plugins/flutter_native_splash/ios"
 | 
				
			||||||
  flutter_platform_alert:
 | 
					  flutter_platform_alert:
 | 
				
			||||||
@@ -320,6 +328,8 @@ EXTERNAL SOURCES:
 | 
				
			|||||||
    :path: ".symlinks/plugins/pasteboard/ios"
 | 
					    :path: ".symlinks/plugins/pasteboard/ios"
 | 
				
			||||||
  path_provider_foundation:
 | 
					  path_provider_foundation:
 | 
				
			||||||
    :path: ".symlinks/plugins/path_provider_foundation/darwin"
 | 
					    :path: ".symlinks/plugins/path_provider_foundation/darwin"
 | 
				
			||||||
 | 
					  pointer_interceptor_ios:
 | 
				
			||||||
 | 
					    :path: ".symlinks/plugins/pointer_interceptor_ios/ios"
 | 
				
			||||||
  receive_sharing_intent:
 | 
					  receive_sharing_intent:
 | 
				
			||||||
    :path: ".symlinks/plugins/receive_sharing_intent/ios"
 | 
					    :path: ".symlinks/plugins/receive_sharing_intent/ios"
 | 
				
			||||||
  record_ios:
 | 
					  record_ios:
 | 
				
			||||||
@@ -351,15 +361,16 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
 | 
					  DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
 | 
				
			||||||
  DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
 | 
					  DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
 | 
				
			||||||
  file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
 | 
					  file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
 | 
				
			||||||
  Firebase: 3435bc66b4d494c2f22c79fd3aae4c1db6662327
 | 
					  Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
 | 
				
			||||||
  firebase_core: 700bac7ed92bb754fd70fbf01d72b36ecdd6d450
 | 
					  firebase_core: c727a02c560a53f1f1e56e18f16515eb5753c492
 | 
				
			||||||
  firebase_messaging: 860c017fcfbb5e27c163062d1d3135388f3ef954
 | 
					  firebase_messaging: 4158969b04b667f5435731ec9d6e453bb58b0c4c
 | 
				
			||||||
  FirebaseCore: c692c7f1c75305ab6aff2b367f25e11d73aa8bd0
 | 
					  FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
 | 
				
			||||||
  FirebaseCoreInternal: 29d7b3af4aaf0b8f3ed20b568c13df399b06f68c
 | 
					  FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
 | 
				
			||||||
  FirebaseInstallations: 0ee9074f2c1e86561ace168ee1470dc67aabaf02
 | 
					  FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
 | 
				
			||||||
  FirebaseMessaging: 195bbdb73e6ca1dbc76cd46e73f3552c084ef6e4
 | 
					  FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
 | 
				
			||||||
  Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
 | 
					  Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
 | 
				
			||||||
  flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
 | 
					  flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
 | 
				
			||||||
  flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
 | 
					  flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
 | 
				
			||||||
  flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
 | 
					  flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
 | 
				
			||||||
  flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
 | 
					  flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
 | 
				
			||||||
@@ -371,8 +382,8 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
 | 
					  GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
 | 
				
			||||||
  image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
 | 
					  image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
 | 
				
			||||||
  irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
 | 
					  irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
 | 
				
			||||||
  Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5
 | 
					  Kingfisher: ff82cb91d9266ddb56cbb2f72d32c26f00d3e5be
 | 
				
			||||||
  livekit_client: 9e901890552514206e5ff828903ed271531da264
 | 
					  livekit_client: 3f79d79233a5bd13d5b541732624ef959d7c538e
 | 
				
			||||||
  local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
 | 
					  local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
 | 
				
			||||||
  media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
 | 
					  media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
 | 
				
			||||||
  media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
 | 
					  media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
 | 
				
			||||||
@@ -382,6 +393,7 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
 | 
					  package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
 | 
				
			||||||
  pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
 | 
					  pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
 | 
				
			||||||
  path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
 | 
					  path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
 | 
				
			||||||
 | 
					  pointer_interceptor_ios: ec847ef8b0915778bed2b2cef636f4d177fa8eed
 | 
				
			||||||
  PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
 | 
					  PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
 | 
				
			||||||
  receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
 | 
					  receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
 | 
				
			||||||
  record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
 | 
					  record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										34
									
								
								lib/models/auto_completion.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								lib/models/auto_completion.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import 'package:freezed_annotation/freezed_annotation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'auto_completion.freezed.dart';
 | 
				
			||||||
 | 
					part 'auto_completion.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class AutoCompletionResponse with _$AutoCompletionResponse {
 | 
				
			||||||
 | 
					  const factory AutoCompletionResponse.account({
 | 
				
			||||||
 | 
					    required String type,
 | 
				
			||||||
 | 
					    required List<AutoCompletionItem> items,
 | 
				
			||||||
 | 
					  }) = AutoCompletionAccountResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const factory AutoCompletionResponse.sticker({
 | 
				
			||||||
 | 
					    required String type,
 | 
				
			||||||
 | 
					    required List<AutoCompletionItem> items,
 | 
				
			||||||
 | 
					  }) = AutoCompletionStickerResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory AutoCompletionResponse.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$AutoCompletionResponseFromJson(json);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class AutoCompletionItem with _$AutoCompletionItem {
 | 
				
			||||||
 | 
					  const factory AutoCompletionItem({
 | 
				
			||||||
 | 
					    required String id,
 | 
				
			||||||
 | 
					    required String displayName,
 | 
				
			||||||
 | 
					    required String? secondaryText,
 | 
				
			||||||
 | 
					    required String type,
 | 
				
			||||||
 | 
					    required dynamic data,
 | 
				
			||||||
 | 
					  }) = _AutoCompletionItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory AutoCompletionItem.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$AutoCompletionItemFromJson(json);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										410
									
								
								lib/models/auto_completion.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								lib/models/auto_completion.freezed.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,410 @@
 | 
				
			|||||||
 | 
					// dart format width=80
 | 
				
			||||||
 | 
					// coverage:ignore-file
 | 
				
			||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'auto_completion.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// FreezedGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dart format off
 | 
				
			||||||
 | 
					T _$identity<T>(T value) => value;
 | 
				
			||||||
 | 
					AutoCompletionResponse _$AutoCompletionResponseFromJson(
 | 
				
			||||||
 | 
					  Map<String, dynamic> json
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					        switch (json['runtimeType']) {
 | 
				
			||||||
 | 
					                  case 'account':
 | 
				
			||||||
 | 
					          return AutoCompletionAccountResponse.fromJson(
 | 
				
			||||||
 | 
					            json
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					                case 'sticker':
 | 
				
			||||||
 | 
					          return AutoCompletionStickerResponse.fromJson(
 | 
				
			||||||
 | 
					            json
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            throw CheckedFromJsonException(
 | 
				
			||||||
 | 
					  json,
 | 
				
			||||||
 | 
					  'runtimeType',
 | 
				
			||||||
 | 
					  'AutoCompletionResponse',
 | 
				
			||||||
 | 
					  'Invalid union type "${json['runtimeType']}"!'
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$AutoCompletionResponse {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 String get type; List<AutoCompletionItem> get items;
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionResponse
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$AutoCompletionResponseCopyWith<AutoCompletionResponse> get copyWith => _$AutoCompletionResponseCopyWithImpl<AutoCompletionResponse>(this as AutoCompletionResponse, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this AutoCompletionResponse to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionResponse&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.items, items));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(items));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'AutoCompletionResponse(type: $type, items: $items)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $AutoCompletionResponseCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $AutoCompletionResponseCopyWith(AutoCompletionResponse value, $Res Function(AutoCompletionResponse) _then) = _$AutoCompletionResponseCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String type, List<AutoCompletionItem> items
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$AutoCompletionResponseCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $AutoCompletionResponseCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$AutoCompletionResponseCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final AutoCompletionResponse _self;
 | 
				
			||||||
 | 
					  final $Res Function(AutoCompletionResponse) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionResponse
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? items = null,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as List<AutoCompletionItem>,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AutoCompletionAccountResponse implements AutoCompletionResponse {
 | 
				
			||||||
 | 
					  const AutoCompletionAccountResponse({required this.type, required final  List<AutoCompletionItem> items, final  String? $type}): _items = items,$type = $type ?? 'account';
 | 
				
			||||||
 | 
					  factory AutoCompletionAccountResponse.fromJson(Map<String, dynamic> json) => _$AutoCompletionAccountResponseFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String type;
 | 
				
			||||||
 | 
					 final  List<AutoCompletionItem> _items;
 | 
				
			||||||
 | 
					@override List<AutoCompletionItem> get items {
 | 
				
			||||||
 | 
					  if (_items is EqualUnmodifiableListView) return _items;
 | 
				
			||||||
 | 
					  // ignore: implicit_dynamic_type
 | 
				
			||||||
 | 
					  return EqualUnmodifiableListView(_items);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(name: 'runtimeType')
 | 
				
			||||||
 | 
					final String $type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionResponse
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$AutoCompletionAccountResponseCopyWith<AutoCompletionAccountResponse> get copyWith => _$AutoCompletionAccountResponseCopyWithImpl<AutoCompletionAccountResponse>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$AutoCompletionAccountResponseToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionAccountResponse&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._items, _items));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_items));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'AutoCompletionResponse.account(type: $type, items: $items)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $AutoCompletionAccountResponseCopyWith<$Res> implements $AutoCompletionResponseCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory $AutoCompletionAccountResponseCopyWith(AutoCompletionAccountResponse value, $Res Function(AutoCompletionAccountResponse) _then) = _$AutoCompletionAccountResponseCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String type, List<AutoCompletionItem> items
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$AutoCompletionAccountResponseCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $AutoCompletionAccountResponseCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$AutoCompletionAccountResponseCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final AutoCompletionAccountResponse _self;
 | 
				
			||||||
 | 
					  final $Res Function(AutoCompletionAccountResponse) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionResponse
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? items = null,}) {
 | 
				
			||||||
 | 
					  return _then(AutoCompletionAccountResponse(
 | 
				
			||||||
 | 
					type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as List<AutoCompletionItem>,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AutoCompletionStickerResponse implements AutoCompletionResponse {
 | 
				
			||||||
 | 
					  const AutoCompletionStickerResponse({required this.type, required final  List<AutoCompletionItem> items, final  String? $type}): _items = items,$type = $type ?? 'sticker';
 | 
				
			||||||
 | 
					  factory AutoCompletionStickerResponse.fromJson(Map<String, dynamic> json) => _$AutoCompletionStickerResponseFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String type;
 | 
				
			||||||
 | 
					 final  List<AutoCompletionItem> _items;
 | 
				
			||||||
 | 
					@override List<AutoCompletionItem> get items {
 | 
				
			||||||
 | 
					  if (_items is EqualUnmodifiableListView) return _items;
 | 
				
			||||||
 | 
					  // ignore: implicit_dynamic_type
 | 
				
			||||||
 | 
					  return EqualUnmodifiableListView(_items);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(name: 'runtimeType')
 | 
				
			||||||
 | 
					final String $type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionResponse
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$AutoCompletionStickerResponseCopyWith<AutoCompletionStickerResponse> get copyWith => _$AutoCompletionStickerResponseCopyWithImpl<AutoCompletionStickerResponse>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$AutoCompletionStickerResponseToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionStickerResponse&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._items, _items));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_items));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'AutoCompletionResponse.sticker(type: $type, items: $items)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $AutoCompletionStickerResponseCopyWith<$Res> implements $AutoCompletionResponseCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory $AutoCompletionStickerResponseCopyWith(AutoCompletionStickerResponse value, $Res Function(AutoCompletionStickerResponse) _then) = _$AutoCompletionStickerResponseCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String type, List<AutoCompletionItem> items
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$AutoCompletionStickerResponseCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $AutoCompletionStickerResponseCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$AutoCompletionStickerResponseCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final AutoCompletionStickerResponse _self;
 | 
				
			||||||
 | 
					  final $Res Function(AutoCompletionStickerResponse) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionResponse
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? items = null,}) {
 | 
				
			||||||
 | 
					  return _then(AutoCompletionStickerResponse(
 | 
				
			||||||
 | 
					type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as List<AutoCompletionItem>,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$AutoCompletionItem {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 String get id; String get displayName; String? get secondaryText; String get type; dynamic get data;
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionItem
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$AutoCompletionItemCopyWith<AutoCompletionItem> get copyWith => _$AutoCompletionItemCopyWithImpl<AutoCompletionItem>(this as AutoCompletionItem, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this AutoCompletionItem to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionItem&&(identical(other.id, id) || other.id == id)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.secondaryText, secondaryText) || other.secondaryText == secondaryText)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.data, data));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,displayName,secondaryText,type,const DeepCollectionEquality().hash(data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'AutoCompletionItem(id: $id, displayName: $displayName, secondaryText: $secondaryText, type: $type, data: $data)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $AutoCompletionItemCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $AutoCompletionItemCopyWith(AutoCompletionItem value, $Res Function(AutoCompletionItem) _then) = _$AutoCompletionItemCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String displayName, String? secondaryText, String type, dynamic data
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$AutoCompletionItemCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $AutoCompletionItemCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$AutoCompletionItemCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final AutoCompletionItem _self;
 | 
				
			||||||
 | 
					  final $Res Function(AutoCompletionItem) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionItem
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? displayName = null,Object? secondaryText = freezed,Object? type = null,Object? data = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,secondaryText: freezed == secondaryText ? _self.secondaryText : secondaryText // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as dynamic,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _AutoCompletionItem implements AutoCompletionItem {
 | 
				
			||||||
 | 
					  const _AutoCompletionItem({required this.id, required this.displayName, required this.secondaryText, required this.type, required this.data});
 | 
				
			||||||
 | 
					  factory _AutoCompletionItem.fromJson(Map<String, dynamic> json) => _$AutoCompletionItemFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String id;
 | 
				
			||||||
 | 
					@override final  String displayName;
 | 
				
			||||||
 | 
					@override final  String? secondaryText;
 | 
				
			||||||
 | 
					@override final  String type;
 | 
				
			||||||
 | 
					@override final  dynamic data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionItem
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					_$AutoCompletionItemCopyWith<_AutoCompletionItem> get copyWith => __$AutoCompletionItemCopyWithImpl<_AutoCompletionItem>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$AutoCompletionItemToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _AutoCompletionItem&&(identical(other.id, id) || other.id == id)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.secondaryText, secondaryText) || other.secondaryText == secondaryText)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.data, data));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,displayName,secondaryText,type,const DeepCollectionEquality().hash(data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'AutoCompletionItem(id: $id, displayName: $displayName, secondaryText: $secondaryText, type: $type, data: $data)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class _$AutoCompletionItemCopyWith<$Res> implements $AutoCompletionItemCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory _$AutoCompletionItemCopyWith(_AutoCompletionItem value, $Res Function(_AutoCompletionItem) _then) = __$AutoCompletionItemCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String displayName, String? secondaryText, String type, dynamic data
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class __$AutoCompletionItemCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements _$AutoCompletionItemCopyWith<$Res> {
 | 
				
			||||||
 | 
					  __$AutoCompletionItemCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final _AutoCompletionItem _self;
 | 
				
			||||||
 | 
					  final $Res Function(_AutoCompletionItem) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of AutoCompletionItem
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? displayName = null,Object? secondaryText = freezed,Object? type = null,Object? data = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_AutoCompletionItem(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,secondaryText: freezed == secondaryText ? _self.secondaryText : secondaryText // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as dynamic,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dart format on
 | 
				
			||||||
							
								
								
									
										63
									
								
								lib/models/auto_completion.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								lib/models/auto_completion.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'auto_completion.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// JsonSerializableGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AutoCompletionAccountResponse _$AutoCompletionAccountResponseFromJson(
 | 
				
			||||||
 | 
					  Map<String, dynamic> json,
 | 
				
			||||||
 | 
					) => AutoCompletionAccountResponse(
 | 
				
			||||||
 | 
					  type: json['type'] as String,
 | 
				
			||||||
 | 
					  items:
 | 
				
			||||||
 | 
					      (json['items'] as List<dynamic>)
 | 
				
			||||||
 | 
					          .map((e) => AutoCompletionItem.fromJson(e as Map<String, dynamic>))
 | 
				
			||||||
 | 
					          .toList(),
 | 
				
			||||||
 | 
					  $type: json['runtimeType'] as String?,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$AutoCompletionAccountResponseToJson(
 | 
				
			||||||
 | 
					  AutoCompletionAccountResponse instance,
 | 
				
			||||||
 | 
					) => <String, dynamic>{
 | 
				
			||||||
 | 
					  'type': instance.type,
 | 
				
			||||||
 | 
					  'items': instance.items.map((e) => e.toJson()).toList(),
 | 
				
			||||||
 | 
					  'runtimeType': instance.$type,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AutoCompletionStickerResponse _$AutoCompletionStickerResponseFromJson(
 | 
				
			||||||
 | 
					  Map<String, dynamic> json,
 | 
				
			||||||
 | 
					) => AutoCompletionStickerResponse(
 | 
				
			||||||
 | 
					  type: json['type'] as String,
 | 
				
			||||||
 | 
					  items:
 | 
				
			||||||
 | 
					      (json['items'] as List<dynamic>)
 | 
				
			||||||
 | 
					          .map((e) => AutoCompletionItem.fromJson(e as Map<String, dynamic>))
 | 
				
			||||||
 | 
					          .toList(),
 | 
				
			||||||
 | 
					  $type: json['runtimeType'] as String?,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$AutoCompletionStickerResponseToJson(
 | 
				
			||||||
 | 
					  AutoCompletionStickerResponse instance,
 | 
				
			||||||
 | 
					) => <String, dynamic>{
 | 
				
			||||||
 | 
					  'type': instance.type,
 | 
				
			||||||
 | 
					  'items': instance.items.map((e) => e.toJson()).toList(),
 | 
				
			||||||
 | 
					  'runtimeType': instance.$type,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_AutoCompletionItem _$AutoCompletionItemFromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					    _AutoCompletionItem(
 | 
				
			||||||
 | 
					      id: json['id'] as String,
 | 
				
			||||||
 | 
					      displayName: json['display_name'] as String,
 | 
				
			||||||
 | 
					      secondaryText: json['secondary_text'] as String?,
 | 
				
			||||||
 | 
					      type: json['type'] as String,
 | 
				
			||||||
 | 
					      data: json['data'],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$AutoCompletionItemToJson(_AutoCompletionItem instance) =>
 | 
				
			||||||
 | 
					    <String, dynamic>{
 | 
				
			||||||
 | 
					      'id': instance.id,
 | 
				
			||||||
 | 
					      'display_name': instance.displayName,
 | 
				
			||||||
 | 
					      'secondary_text': instance.secondaryText,
 | 
				
			||||||
 | 
					      'type': instance.type,
 | 
				
			||||||
 | 
					      'data': instance.data,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
							
								
								
									
										64
									
								
								lib/models/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/models/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					import 'dart:convert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:freezed_annotation/freezed_annotation.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/embed.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'webfeed.freezed.dart';
 | 
				
			||||||
 | 
					part 'webfeed.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class SnWebFeedConfig with _$SnWebFeedConfig {
 | 
				
			||||||
 | 
					  const factory SnWebFeedConfig({@Default(false) bool scrapPage}) =
 | 
				
			||||||
 | 
					      _SnWebFeedConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory SnWebFeedConfig.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$SnWebFeedConfigFromJson(json);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class SnWebFeed with _$SnWebFeed {
 | 
				
			||||||
 | 
					  const factory SnWebFeed({
 | 
				
			||||||
 | 
					    required String id,
 | 
				
			||||||
 | 
					    required String url,
 | 
				
			||||||
 | 
					    required String title,
 | 
				
			||||||
 | 
					    String? description,
 | 
				
			||||||
 | 
					    SnScrappedLink? preview,
 | 
				
			||||||
 | 
					    @Default(SnWebFeedConfig()) SnWebFeedConfig config,
 | 
				
			||||||
 | 
					    required String publisherId,
 | 
				
			||||||
 | 
					    @Default([]) List<SnWebArticle> articles,
 | 
				
			||||||
 | 
					    required DateTime createdAt,
 | 
				
			||||||
 | 
					    required DateTime updatedAt,
 | 
				
			||||||
 | 
					    DateTime? deletedAt,
 | 
				
			||||||
 | 
					  }) = _SnWebFeed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory SnWebFeed.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$SnWebFeedFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory SnWebFeed.fromJsonString(String jsonString) =>
 | 
				
			||||||
 | 
					      SnWebFeed.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class SnWebArticle with _$SnWebArticle {
 | 
				
			||||||
 | 
					  const factory SnWebArticle({
 | 
				
			||||||
 | 
					    required String id,
 | 
				
			||||||
 | 
					    required String title,
 | 
				
			||||||
 | 
					    required String url,
 | 
				
			||||||
 | 
					    String? author,
 | 
				
			||||||
 | 
					    Map<String, dynamic>? meta,
 | 
				
			||||||
 | 
					    SnScrappedLink? preview,
 | 
				
			||||||
 | 
					    SnWebFeed? feed,
 | 
				
			||||||
 | 
					    String? content,
 | 
				
			||||||
 | 
					    DateTime? publishedAt,
 | 
				
			||||||
 | 
					    required String feedId,
 | 
				
			||||||
 | 
					    required DateTime createdAt,
 | 
				
			||||||
 | 
					    required DateTime updatedAt,
 | 
				
			||||||
 | 
					    DateTime? deletedAt,
 | 
				
			||||||
 | 
					  }) = _SnWebArticle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory SnWebArticle.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$SnWebArticleFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory SnWebArticle.fromJsonString(String jsonString) =>
 | 
				
			||||||
 | 
					      SnWebArticle.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										584
									
								
								lib/models/webfeed.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										584
									
								
								lib/models/webfeed.freezed.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,584 @@
 | 
				
			|||||||
 | 
					// dart format width=80
 | 
				
			||||||
 | 
					// coverage:ignore-file
 | 
				
			||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'webfeed.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// FreezedGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dart format off
 | 
				
			||||||
 | 
					T _$identity<T>(T value) => value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$SnWebFeedConfig {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 bool get scrapPage;
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeedConfig
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnWebFeedConfigCopyWith<SnWebFeedConfig> get copyWith => _$SnWebFeedConfigCopyWithImpl<SnWebFeedConfig>(this as SnWebFeedConfig, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this SnWebFeedConfig to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,scrapPage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnWebFeedConfig(scrapPage: $scrapPage)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $SnWebFeedConfigCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $SnWebFeedConfigCopyWith(SnWebFeedConfig value, $Res Function(SnWebFeedConfig) _then) = _$SnWebFeedConfigCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 bool scrapPage
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$SnWebFeedConfigCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $SnWebFeedConfigCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$SnWebFeedConfigCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final SnWebFeedConfig _self;
 | 
				
			||||||
 | 
					  final $Res Function(SnWebFeedConfig) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeedConfig
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? scrapPage = null,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					scrapPage: null == scrapPage ? _self.scrapPage : scrapPage // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as bool,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SnWebFeedConfig implements SnWebFeedConfig {
 | 
				
			||||||
 | 
					  const _SnWebFeedConfig({this.scrapPage = false});
 | 
				
			||||||
 | 
					  factory _SnWebFeedConfig.fromJson(Map<String, dynamic> json) => _$SnWebFeedConfigFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override@JsonKey() final  bool scrapPage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeedConfig
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					_$SnWebFeedConfigCopyWith<_SnWebFeedConfig> get copyWith => __$SnWebFeedConfigCopyWithImpl<_SnWebFeedConfig>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$SnWebFeedConfigToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,scrapPage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnWebFeedConfig(scrapPage: $scrapPage)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class _$SnWebFeedConfigCopyWith<$Res> implements $SnWebFeedConfigCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory _$SnWebFeedConfigCopyWith(_SnWebFeedConfig value, $Res Function(_SnWebFeedConfig) _then) = __$SnWebFeedConfigCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 bool scrapPage
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class __$SnWebFeedConfigCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements _$SnWebFeedConfigCopyWith<$Res> {
 | 
				
			||||||
 | 
					  __$SnWebFeedConfigCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final _SnWebFeedConfig _self;
 | 
				
			||||||
 | 
					  final $Res Function(_SnWebFeedConfig) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeedConfig
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? scrapPage = null,}) {
 | 
				
			||||||
 | 
					  return _then(_SnWebFeedConfig(
 | 
				
			||||||
 | 
					scrapPage: null == scrapPage ? _self.scrapPage : scrapPage // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as bool,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$SnWebFeed {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 String get id; String get url; String get title; String? get description; SnScrappedLink? get preview; SnWebFeedConfig get config; String get publisherId; List<SnWebArticle> get articles; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnWebFeedCopyWith<SnWebFeed> get copyWith => _$SnWebFeedCopyWithImpl<SnWebFeed>(this as SnWebFeed, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this SnWebFeed to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other.articles, articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,url,title,description,preview,config,publisherId,const DeepCollectionEquality().hash(articles),createdAt,updatedAt,deletedAt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnWebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $SnWebFeedCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $SnWebFeedCopyWith(SnWebFeed value, $Res Function(SnWebFeed) _then) = _$SnWebFeedCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String url, String title, String? description, SnScrappedLink? preview, SnWebFeedConfig config, String publisherId, List<SnWebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$SnScrappedLinkCopyWith<$Res>? get preview;$SnWebFeedConfigCopyWith<$Res> get config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$SnWebFeedCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $SnWebFeedCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$SnWebFeedCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final SnWebFeed _self;
 | 
				
			||||||
 | 
					  final $Res Function(SnWebFeed) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnWebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,articles: null == articles ? _self.articles : articles // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as List<SnWebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnScrappedLinkCopyWith<$Res>? get preview {
 | 
				
			||||||
 | 
					    if (_self.preview == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(preview: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnWebFeedConfigCopyWith<$Res> get config {
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return $SnWebFeedConfigCopyWith<$Res>(_self.config, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(config: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SnWebFeed implements SnWebFeed {
 | 
				
			||||||
 | 
					  const _SnWebFeed({required this.id, required this.url, required this.title, this.description, this.preview, this.config = const SnWebFeedConfig(), required this.publisherId, final  List<SnWebArticle> articles = const [], required this.createdAt, required this.updatedAt, this.deletedAt}): _articles = articles;
 | 
				
			||||||
 | 
					  factory _SnWebFeed.fromJson(Map<String, dynamic> json) => _$SnWebFeedFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String id;
 | 
				
			||||||
 | 
					@override final  String url;
 | 
				
			||||||
 | 
					@override final  String title;
 | 
				
			||||||
 | 
					@override final  String? description;
 | 
				
			||||||
 | 
					@override final  SnScrappedLink? preview;
 | 
				
			||||||
 | 
					@override@JsonKey() final  SnWebFeedConfig config;
 | 
				
			||||||
 | 
					@override final  String publisherId;
 | 
				
			||||||
 | 
					 final  List<SnWebArticle> _articles;
 | 
				
			||||||
 | 
					@override@JsonKey() List<SnWebArticle> get articles {
 | 
				
			||||||
 | 
					  if (_articles is EqualUnmodifiableListView) return _articles;
 | 
				
			||||||
 | 
					  // ignore: implicit_dynamic_type
 | 
				
			||||||
 | 
					  return EqualUnmodifiableListView(_articles);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  DateTime createdAt;
 | 
				
			||||||
 | 
					@override final  DateTime updatedAt;
 | 
				
			||||||
 | 
					@override final  DateTime? deletedAt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					_$SnWebFeedCopyWith<_SnWebFeed> get copyWith => __$SnWebFeedCopyWithImpl<_SnWebFeed>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$SnWebFeedToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other._articles, _articles)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,url,title,description,preview,config,publisherId,const DeepCollectionEquality().hash(_articles),createdAt,updatedAt,deletedAt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnWebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class _$SnWebFeedCopyWith<$Res> implements $SnWebFeedCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory _$SnWebFeedCopyWith(_SnWebFeed value, $Res Function(_SnWebFeed) _then) = __$SnWebFeedCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String url, String title, String? description, SnScrappedLink? preview, SnWebFeedConfig config, String publisherId, List<SnWebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override $SnScrappedLinkCopyWith<$Res>? get preview;@override $SnWebFeedConfigCopyWith<$Res> get config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class __$SnWebFeedCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements _$SnWebFeedCopyWith<$Res> {
 | 
				
			||||||
 | 
					  __$SnWebFeedCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final _SnWebFeed _self;
 | 
				
			||||||
 | 
					  final $Res Function(_SnWebFeed) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_SnWebFeed(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnWebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,articles: null == articles ? _self._articles : articles // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as List<SnWebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnScrappedLinkCopyWith<$Res>? get preview {
 | 
				
			||||||
 | 
					    if (_self.preview == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(preview: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}/// Create a copy of SnWebFeed
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnWebFeedConfigCopyWith<$Res> get config {
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return $SnWebFeedConfigCopyWith<$Res>(_self.config, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(config: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$SnWebArticle {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 String get id; String get title; String get url; String? get author; Map<String, dynamic>? get meta; SnScrappedLink? get preview; SnWebFeed? get feed; String? get content; DateTime? get publishedAt; String get feedId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
 | 
				
			||||||
 | 
					/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnWebArticleCopyWith<SnWebArticle> get copyWith => _$SnWebArticleCopyWithImpl<SnWebArticle>(this as SnWebArticle, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this SnWebArticle to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.feed, feed) || other.feed == feed)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(meta),preview,feed,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnWebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, feed: $feed, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $SnWebArticleCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $SnWebArticleCopyWith(SnWebArticle value, $Res Function(SnWebArticle) _then) = _$SnWebArticleCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, SnWebFeed? feed, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$SnScrappedLinkCopyWith<$Res>? get preview;$SnWebFeedCopyWith<$Res>? get feed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$SnWebArticleCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $SnWebArticleCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$SnWebArticleCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final SnWebArticle _self;
 | 
				
			||||||
 | 
					  final $Res Function(SnWebArticle) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? feed = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnScrappedLink?,feed: freezed == feed ? _self.feed : feed // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnWebFeed?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnScrappedLinkCopyWith<$Res>? get preview {
 | 
				
			||||||
 | 
					    if (_self.preview == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(preview: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnWebFeedCopyWith<$Res>? get feed {
 | 
				
			||||||
 | 
					    if (_self.feed == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnWebFeedCopyWith<$Res>(_self.feed!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(feed: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SnWebArticle implements SnWebArticle {
 | 
				
			||||||
 | 
					  const _SnWebArticle({required this.id, required this.title, required this.url, this.author, final  Map<String, dynamic>? meta, this.preview, this.feed, this.content, this.publishedAt, required this.feedId, required this.createdAt, required this.updatedAt, this.deletedAt}): _meta = meta;
 | 
				
			||||||
 | 
					  factory _SnWebArticle.fromJson(Map<String, dynamic> json) => _$SnWebArticleFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String id;
 | 
				
			||||||
 | 
					@override final  String title;
 | 
				
			||||||
 | 
					@override final  String url;
 | 
				
			||||||
 | 
					@override final  String? author;
 | 
				
			||||||
 | 
					 final  Map<String, dynamic>? _meta;
 | 
				
			||||||
 | 
					@override Map<String, dynamic>? get meta {
 | 
				
			||||||
 | 
					  final value = _meta;
 | 
				
			||||||
 | 
					  if (value == null) return null;
 | 
				
			||||||
 | 
					  if (_meta is EqualUnmodifiableMapView) return _meta;
 | 
				
			||||||
 | 
					  // ignore: implicit_dynamic_type
 | 
				
			||||||
 | 
					  return EqualUnmodifiableMapView(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  SnScrappedLink? preview;
 | 
				
			||||||
 | 
					@override final  SnWebFeed? feed;
 | 
				
			||||||
 | 
					@override final  String? content;
 | 
				
			||||||
 | 
					@override final  DateTime? publishedAt;
 | 
				
			||||||
 | 
					@override final  String feedId;
 | 
				
			||||||
 | 
					@override final  DateTime createdAt;
 | 
				
			||||||
 | 
					@override final  DateTime updatedAt;
 | 
				
			||||||
 | 
					@override final  DateTime? deletedAt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					_$SnWebArticleCopyWith<_SnWebArticle> get copyWith => __$SnWebArticleCopyWithImpl<_SnWebArticle>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$SnWebArticleToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.feed, feed) || other.feed == feed)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,title,url,author,const DeepCollectionEquality().hash(_meta),preview,feed,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnWebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, feed: $feed, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class _$SnWebArticleCopyWith<$Res> implements $SnWebArticleCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory _$SnWebArticleCopyWith(_SnWebArticle value, $Res Function(_SnWebArticle) _then) = __$SnWebArticleCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, SnWebFeed? feed, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override $SnScrappedLinkCopyWith<$Res>? get preview;@override $SnWebFeedCopyWith<$Res>? get feed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class __$SnWebArticleCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements _$SnWebArticleCopyWith<$Res> {
 | 
				
			||||||
 | 
					  __$SnWebArticleCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final _SnWebArticle _self;
 | 
				
			||||||
 | 
					  final $Res Function(_SnWebArticle) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? feed = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_SnWebArticle(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnScrappedLink?,feed: freezed == feed ? _self.feed : feed // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnWebFeed?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as DateTime?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnScrappedLinkCopyWith<$Res>? get preview {
 | 
				
			||||||
 | 
					    if (_self.preview == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(preview: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}/// Create a copy of SnWebArticle
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnWebFeedCopyWith<$Res>? get feed {
 | 
				
			||||||
 | 
					    if (_self.feed == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnWebFeedCopyWith<$Res>(_self.feed!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(feed: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dart format on
 | 
				
			||||||
							
								
								
									
										103
									
								
								lib/models/webfeed.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/models/webfeed.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'webfeed.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// JsonSerializableGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_SnWebFeedConfig _$SnWebFeedConfigFromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					    _SnWebFeedConfig(scrapPage: json['scrap_page'] as bool? ?? false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$SnWebFeedConfigToJson(_SnWebFeedConfig instance) =>
 | 
				
			||||||
 | 
					    <String, dynamic>{'scrap_page': instance.scrapPage};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_SnWebFeed _$SnWebFeedFromJson(Map<String, dynamic> json) => _SnWebFeed(
 | 
				
			||||||
 | 
					  id: json['id'] as String,
 | 
				
			||||||
 | 
					  url: json['url'] as String,
 | 
				
			||||||
 | 
					  title: json['title'] as String,
 | 
				
			||||||
 | 
					  description: json['description'] as String?,
 | 
				
			||||||
 | 
					  preview:
 | 
				
			||||||
 | 
					      json['preview'] == null
 | 
				
			||||||
 | 
					          ? null
 | 
				
			||||||
 | 
					          : SnScrappedLink.fromJson(json['preview'] as Map<String, dynamic>),
 | 
				
			||||||
 | 
					  config:
 | 
				
			||||||
 | 
					      json['config'] == null
 | 
				
			||||||
 | 
					          ? const SnWebFeedConfig()
 | 
				
			||||||
 | 
					          : SnWebFeedConfig.fromJson(json['config'] as Map<String, dynamic>),
 | 
				
			||||||
 | 
					  publisherId: json['publisher_id'] as String,
 | 
				
			||||||
 | 
					  articles:
 | 
				
			||||||
 | 
					      (json['articles'] as List<dynamic>?)
 | 
				
			||||||
 | 
					          ?.map((e) => SnWebArticle.fromJson(e as Map<String, dynamic>))
 | 
				
			||||||
 | 
					          .toList() ??
 | 
				
			||||||
 | 
					      const [],
 | 
				
			||||||
 | 
					  createdAt: DateTime.parse(json['created_at'] as String),
 | 
				
			||||||
 | 
					  updatedAt: DateTime.parse(json['updated_at'] as String),
 | 
				
			||||||
 | 
					  deletedAt:
 | 
				
			||||||
 | 
					      json['deleted_at'] == null
 | 
				
			||||||
 | 
					          ? null
 | 
				
			||||||
 | 
					          : DateTime.parse(json['deleted_at'] as String),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$SnWebFeedToJson(_SnWebFeed instance) =>
 | 
				
			||||||
 | 
					    <String, dynamic>{
 | 
				
			||||||
 | 
					      'id': instance.id,
 | 
				
			||||||
 | 
					      'url': instance.url,
 | 
				
			||||||
 | 
					      'title': instance.title,
 | 
				
			||||||
 | 
					      'description': instance.description,
 | 
				
			||||||
 | 
					      'preview': instance.preview?.toJson(),
 | 
				
			||||||
 | 
					      'config': instance.config.toJson(),
 | 
				
			||||||
 | 
					      'publisher_id': instance.publisherId,
 | 
				
			||||||
 | 
					      'articles': instance.articles.map((e) => e.toJson()).toList(),
 | 
				
			||||||
 | 
					      'created_at': instance.createdAt.toIso8601String(),
 | 
				
			||||||
 | 
					      'updated_at': instance.updatedAt.toIso8601String(),
 | 
				
			||||||
 | 
					      'deleted_at': instance.deletedAt?.toIso8601String(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_SnWebArticle _$SnWebArticleFromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					    _SnWebArticle(
 | 
				
			||||||
 | 
					      id: json['id'] as String,
 | 
				
			||||||
 | 
					      title: json['title'] as String,
 | 
				
			||||||
 | 
					      url: json['url'] as String,
 | 
				
			||||||
 | 
					      author: json['author'] as String?,
 | 
				
			||||||
 | 
					      meta: json['meta'] as Map<String, dynamic>?,
 | 
				
			||||||
 | 
					      preview:
 | 
				
			||||||
 | 
					          json['preview'] == null
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : SnScrappedLink.fromJson(
 | 
				
			||||||
 | 
					                json['preview'] as Map<String, dynamic>,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					      feed:
 | 
				
			||||||
 | 
					          json['feed'] == null
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : SnWebFeed.fromJson(json['feed'] as Map<String, dynamic>),
 | 
				
			||||||
 | 
					      content: json['content'] as String?,
 | 
				
			||||||
 | 
					      publishedAt:
 | 
				
			||||||
 | 
					          json['published_at'] == null
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : DateTime.parse(json['published_at'] as String),
 | 
				
			||||||
 | 
					      feedId: json['feed_id'] as String,
 | 
				
			||||||
 | 
					      createdAt: DateTime.parse(json['created_at'] as String),
 | 
				
			||||||
 | 
					      updatedAt: DateTime.parse(json['updated_at'] as String),
 | 
				
			||||||
 | 
					      deletedAt:
 | 
				
			||||||
 | 
					          json['deleted_at'] == null
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : DateTime.parse(json['deleted_at'] as String),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$SnWebArticleToJson(_SnWebArticle instance) =>
 | 
				
			||||||
 | 
					    <String, dynamic>{
 | 
				
			||||||
 | 
					      'id': instance.id,
 | 
				
			||||||
 | 
					      'title': instance.title,
 | 
				
			||||||
 | 
					      'url': instance.url,
 | 
				
			||||||
 | 
					      'author': instance.author,
 | 
				
			||||||
 | 
					      'meta': instance.meta,
 | 
				
			||||||
 | 
					      'preview': instance.preview?.toJson(),
 | 
				
			||||||
 | 
					      'feed': instance.feed?.toJson(),
 | 
				
			||||||
 | 
					      'content': instance.content,
 | 
				
			||||||
 | 
					      'published_at': instance.publishedAt?.toIso8601String(),
 | 
				
			||||||
 | 
					      'feed_id': instance.feedId,
 | 
				
			||||||
 | 
					      'created_at': instance.createdAt.toIso8601String(),
 | 
				
			||||||
 | 
					      'updated_at': instance.updatedAt.toIso8601String(),
 | 
				
			||||||
 | 
					      'deleted_at': instance.deletedAt?.toIso8601String(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
							
								
								
									
										31
									
								
								lib/pods/article_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/pods/article_detail.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:dio/dio.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Provider that fetches a single article by its ID
 | 
				
			||||||
 | 
					final articleDetailProvider = FutureProvider.autoDispose.family<SnWebArticle, String>(
 | 
				
			||||||
 | 
					  (ref, articleId) async {
 | 
				
			||||||
 | 
					    final dio = ref.watch(apiClientProvider);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final response = await dio.get<Map<String, dynamic>>(
 | 
				
			||||||
 | 
					        '/feeds/articles/$articleId',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (response.statusCode == 200 && response.data != null) {
 | 
				
			||||||
 | 
					        return SnWebArticle.fromJson(response.data!);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw Exception('Failed to load article');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } on DioException catch (e) {
 | 
				
			||||||
 | 
					      if (e.response?.statusCode == 404) {
 | 
				
			||||||
 | 
					        throw Exception('Article not found');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw Exception('Failed to load article: ${e.message}');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      throw Exception('Failed to load article: $e');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										1
									
								
								lib/pods/article_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lib/pods/article_list.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										123
									
								
								lib/pods/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								lib/pods/webfeed.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:dio/dio.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final webFeedListProvider = FutureProvider.family<List<SnWebFeed>, String>((
 | 
				
			||||||
 | 
					  ref,
 | 
				
			||||||
 | 
					  pubName,
 | 
				
			||||||
 | 
					) async {
 | 
				
			||||||
 | 
					  final client = ref.watch(apiClientProvider);
 | 
				
			||||||
 | 
					  final response = await client.get('/publishers/$pubName/feeds');
 | 
				
			||||||
 | 
					  return (response.data as List)
 | 
				
			||||||
 | 
					      .map((json) => SnWebFeed.fromJson(json))
 | 
				
			||||||
 | 
					      .toList();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebFeedNotifier
 | 
				
			||||||
 | 
					    extends
 | 
				
			||||||
 | 
					        AutoDisposeFamilyAsyncNotifier<
 | 
				
			||||||
 | 
					          SnWebFeed,
 | 
				
			||||||
 | 
					          ({String pubName, String? feedId})
 | 
				
			||||||
 | 
					        > {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  FutureOr<SnWebFeed> build(({String pubName, String? feedId}) arg) async {
 | 
				
			||||||
 | 
					    if (arg.feedId == null || arg.feedId!.isEmpty) {
 | 
				
			||||||
 | 
					      return SnWebFeed(
 | 
				
			||||||
 | 
					        id: '',
 | 
				
			||||||
 | 
					        url: '',
 | 
				
			||||||
 | 
					        title: '',
 | 
				
			||||||
 | 
					        publisherId: arg.pubName,
 | 
				
			||||||
 | 
					        createdAt: DateTime.now(),
 | 
				
			||||||
 | 
					        updatedAt: DateTime.now(),
 | 
				
			||||||
 | 
					        deletedAt: null,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					      final response = await client.get(
 | 
				
			||||||
 | 
					        '/publishers/${arg.pubName}/feeds/${arg.feedId}',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return SnWebFeed.fromJson(response.data);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      rethrow;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> saveFeed(SnWebFeed feed) async {
 | 
				
			||||||
 | 
					    state = const AsyncValue.loading();
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					      final url = '/publishers/${feed.publisherId}/feeds';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final response =
 | 
				
			||||||
 | 
					          feed.id.isEmpty
 | 
				
			||||||
 | 
					              ? await client.post(url, data: feed.toJson())
 | 
				
			||||||
 | 
					              : await client.patch('$url/${feed.id}', data: feed.toJson());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      state = AsyncValue.data(SnWebFeed.fromJson(response.data));
 | 
				
			||||||
 | 
					    } catch (error, stackTrace) {
 | 
				
			||||||
 | 
					      state = AsyncValue.error(error, stackTrace);
 | 
				
			||||||
 | 
					      rethrow;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> deleteFeed() async {
 | 
				
			||||||
 | 
					    final feedId = arg.feedId;
 | 
				
			||||||
 | 
					    if (feedId == null || feedId.isEmpty) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state = const AsyncValue.loading();
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					      await client.delete('/publishers/${arg.pubName}/feeds/$feedId');
 | 
				
			||||||
 | 
					      state = AsyncValue.data(
 | 
				
			||||||
 | 
					        SnWebFeed(
 | 
				
			||||||
 | 
					          id: '',
 | 
				
			||||||
 | 
					          url: '',
 | 
				
			||||||
 | 
					          title: '',
 | 
				
			||||||
 | 
					          publisherId: arg.pubName,
 | 
				
			||||||
 | 
					          createdAt: DateTime.now(),
 | 
				
			||||||
 | 
					          updatedAt: DateTime.now(),
 | 
				
			||||||
 | 
					          deletedAt: null,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } catch (error, stackTrace) {
 | 
				
			||||||
 | 
					      state = AsyncValue.error(error, stackTrace);
 | 
				
			||||||
 | 
					      rethrow;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> scrapFeed() async {
 | 
				
			||||||
 | 
					    final feedId = arg.feedId;
 | 
				
			||||||
 | 
					    if (feedId == null || feedId.isEmpty) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    state = const AsyncValue.loading();
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					      await client.post(
 | 
				
			||||||
 | 
					        '/publishers/${arg.pubName}/feeds/$feedId/scrap',
 | 
				
			||||||
 | 
					        options: Options(
 | 
				
			||||||
 | 
					          sendTimeout: const Duration(seconds: 60),
 | 
				
			||||||
 | 
					          receiveTimeout: const Duration(seconds: 180),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Reload the feed
 | 
				
			||||||
 | 
					      final response = await client.get(
 | 
				
			||||||
 | 
					        '/publishers/${arg.pubName}/feeds/$feedId',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      state = AsyncValue.data(SnWebFeed.fromJson(response.data));
 | 
				
			||||||
 | 
					    } catch (error, stackTrace) {
 | 
				
			||||||
 | 
					      state = AsyncValue.error(error, stackTrace);
 | 
				
			||||||
 | 
					      rethrow;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final webFeedNotifierProvider = AsyncNotifierProvider.autoDispose
 | 
				
			||||||
 | 
					    .family<WebFeedNotifier, SnWebFeed, ({String pubName, String? feedId})>(
 | 
				
			||||||
 | 
					      WebFeedNotifier.new,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
@@ -1,14 +1,17 @@
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:go_router/go_router.dart';
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/about.dart';
 | 
				
			||||||
import 'package:island/screens/developers/apps.dart';
 | 
					import 'package:island/screens/developers/apps.dart';
 | 
				
			||||||
import 'package:island/screens/developers/edit_app.dart';
 | 
					import 'package:island/screens/developers/edit_app.dart';
 | 
				
			||||||
import 'package:island/screens/developers/new_app.dart';
 | 
					import 'package:island/screens/developers/new_app.dart';
 | 
				
			||||||
import 'package:island/screens/developers/hub.dart';
 | 
					import 'package:island/screens/developers/hub.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/discovery/articles.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/posts/post_search.dart';
 | 
				
			||||||
import 'package:island/widgets/app_wrapper.dart';
 | 
					import 'package:island/widgets/app_wrapper.dart';
 | 
				
			||||||
import 'package:island/screens/tabs.dart';
 | 
					import 'package:island/screens/tabs.dart';
 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:island/screens/explore.dart';
 | 
					import 'package:island/screens/explore.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/article_detail_screen.dart';
 | 
				
			||||||
import 'package:island/screens/account.dart';
 | 
					import 'package:island/screens/account.dart';
 | 
				
			||||||
import 'package:island/screens/notification.dart';
 | 
					import 'package:island/screens/notification.dart';
 | 
				
			||||||
import 'package:island/screens/wallet.dart';
 | 
					import 'package:island/screens/wallet.dart';
 | 
				
			||||||
@@ -22,18 +25,20 @@ import 'package:island/screens/chat/room.dart';
 | 
				
			|||||||
import 'package:island/screens/chat/room_detail.dart';
 | 
					import 'package:island/screens/chat/room_detail.dart';
 | 
				
			||||||
import 'package:island/screens/chat/call.dart';
 | 
					import 'package:island/screens/chat/call.dart';
 | 
				
			||||||
import 'package:island/screens/creators/hub.dart';
 | 
					import 'package:island/screens/creators/hub.dart';
 | 
				
			||||||
import 'package:island/screens/creators/posts/list.dart';
 | 
					import 'package:island/screens/creators/posts/post_manage_list.dart';
 | 
				
			||||||
import 'package:island/screens/creators/stickers/stickers.dart';
 | 
					import 'package:island/screens/creators/stickers/stickers.dart';
 | 
				
			||||||
import 'package:island/screens/creators/stickers/pack_detail.dart';
 | 
					import 'package:island/screens/creators/stickers/pack_detail.dart';
 | 
				
			||||||
import 'package:island/screens/creators/publishers.dart';
 | 
					import 'package:island/screens/creators/publishers.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/creators/webfeed/webfeed_list.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/creators/webfeed/webfeed_edit.dart';
 | 
				
			||||||
import 'package:island/screens/posts/compose.dart';
 | 
					import 'package:island/screens/posts/compose.dart';
 | 
				
			||||||
import 'package:island/screens/posts/detail.dart';
 | 
					import 'package:island/screens/posts/post_detail.dart';
 | 
				
			||||||
import 'package:island/screens/posts/pub_profile.dart';
 | 
					import 'package:island/screens/posts/pub_profile.dart';
 | 
				
			||||||
import 'package:island/screens/auth/login.dart';
 | 
					import 'package:island/screens/auth/login.dart';
 | 
				
			||||||
import 'package:island/screens/auth/create_account.dart';
 | 
					import 'package:island/screens/auth/create_account.dart';
 | 
				
			||||||
import 'package:island/screens/settings.dart';
 | 
					import 'package:island/screens/settings.dart';
 | 
				
			||||||
import 'package:island/screens/realm/realms.dart';
 | 
					import 'package:island/screens/realm/realms.dart';
 | 
				
			||||||
import 'package:island/screens/realm/detail.dart';
 | 
					import 'package:island/screens/realm/realm_detail.dart';
 | 
				
			||||||
import 'package:island/screens/account/event_calendar.dart';
 | 
					import 'package:island/screens/account/event_calendar.dart';
 | 
				
			||||||
import 'package:island/screens/discovery/realms.dart';
 | 
					import 'package:island/screens/discovery/realms.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,6 +96,33 @@ final routerProvider = Provider<GoRouter>((ref) {
 | 
				
			|||||||
                path: '/creators',
 | 
					                path: '/creators',
 | 
				
			||||||
                builder: (context, state) => const CreatorHubScreen(),
 | 
					                builder: (context, state) => const CreatorHubScreen(),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 | 
					              // Web Feed Routes
 | 
				
			||||||
 | 
					              GoRoute(
 | 
				
			||||||
 | 
					                path: '/creators/:name/feeds',
 | 
				
			||||||
 | 
					                builder: (context, state) {
 | 
				
			||||||
 | 
					                  final name = state.pathParameters['name']!;
 | 
				
			||||||
 | 
					                  return WebFeedListScreen(pubName: name);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                routes: [
 | 
				
			||||||
 | 
					                  GoRoute(
 | 
				
			||||||
 | 
					                    path: 'new',
 | 
				
			||||||
 | 
					                    builder: (context, state) {
 | 
				
			||||||
 | 
					                      return WebFeedNewScreen(
 | 
				
			||||||
 | 
					                        pubName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                      );
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  GoRoute(
 | 
				
			||||||
 | 
					                    path: ':feedId',
 | 
				
			||||||
 | 
					                    builder: (context, state) {
 | 
				
			||||||
 | 
					                      return WebFeedEditScreen(
 | 
				
			||||||
 | 
					                        pubName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                        feedId: state.pathParameters['feedId'],
 | 
				
			||||||
 | 
					                      );
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
              GoRoute(
 | 
					              GoRoute(
 | 
				
			||||||
                path: '/creators/:name/posts',
 | 
					                path: '/creators/:name/posts',
 | 
				
			||||||
                builder: (context, state) {
 | 
					                builder: (context, state) {
 | 
				
			||||||
@@ -167,26 +199,42 @@ final routerProvider = Provider<GoRouter>((ref) {
 | 
				
			|||||||
              ),
 | 
					              ),
 | 
				
			||||||
              GoRoute(
 | 
					              GoRoute(
 | 
				
			||||||
                path: '/developers/:name/apps',
 | 
					                path: '/developers/:name/apps',
 | 
				
			||||||
                builder: (context, state) => CustomAppsScreen(
 | 
					                builder:
 | 
				
			||||||
                  publisherName: state.pathParameters['name']!,
 | 
					                    (context, state) => CustomAppsScreen(
 | 
				
			||||||
                ),
 | 
					                      publisherName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              GoRoute(
 | 
					              GoRoute(
 | 
				
			||||||
                path: '/developers/:name/apps/new',
 | 
					                path: '/developers/:name/apps/new',
 | 
				
			||||||
                builder: (context, state) => NewCustomAppScreen(
 | 
					                builder:
 | 
				
			||||||
                  publisherName: state.pathParameters['name']!,
 | 
					                    (context, state) => NewCustomAppScreen(
 | 
				
			||||||
                ),
 | 
					                      publisherName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              GoRoute(
 | 
					              GoRoute(
 | 
				
			||||||
                path: '/developers/:name/apps/:id',
 | 
					                path: '/developers/:name/apps/:id',
 | 
				
			||||||
                builder: (context, state) => EditAppScreen(
 | 
					                builder:
 | 
				
			||||||
                  publisherName: state.pathParameters['name']!,
 | 
					                    (context, state) => EditAppScreen(
 | 
				
			||||||
                  id: state.pathParameters['id']!,
 | 
					                      publisherName: state.pathParameters['name']!,
 | 
				
			||||||
                ),
 | 
					                      id: state.pathParameters['id']!,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Web articles
 | 
				
			||||||
 | 
					          GoRoute(
 | 
				
			||||||
 | 
					            path: '/feeds/articles',
 | 
				
			||||||
 | 
					            builder: (context, state) => const ArticlesScreen(),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          GoRoute(
 | 
				
			||||||
 | 
					            path: '/feeds/articles/:id',
 | 
				
			||||||
 | 
					            builder: (context, state) {
 | 
				
			||||||
 | 
					              final id = state.pathParameters['id']!;
 | 
				
			||||||
 | 
					              return ArticleDetailScreen(articleId: id);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Auth routes
 | 
					          // Auth routes
 | 
				
			||||||
          GoRoute(
 | 
					          GoRoute(
 | 
				
			||||||
            path: '/auth/login',
 | 
					            path: '/auth/login',
 | 
				
			||||||
@@ -202,6 +250,10 @@ final routerProvider = Provider<GoRouter>((ref) {
 | 
				
			|||||||
            path: '/settings',
 | 
					            path: '/settings',
 | 
				
			||||||
            builder: (context, state) => const SettingsScreen(),
 | 
					            builder: (context, state) => const SettingsScreen(),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
					          GoRoute(
 | 
				
			||||||
 | 
					            path: '/about',
 | 
				
			||||||
 | 
					            builder: (context, state) => const AboutScreen(),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Main tabs with TabsScreen shell
 | 
					          // Main tabs with TabsScreen shell
 | 
				
			||||||
          ShellRoute(
 | 
					          ShellRoute(
 | 
				
			||||||
@@ -219,6 +271,10 @@ final routerProvider = Provider<GoRouter>((ref) {
 | 
				
			|||||||
                    path: '/',
 | 
					                    path: '/',
 | 
				
			||||||
                    builder: (context, state) => const ExploreScreen(),
 | 
					                    builder: (context, state) => const ExploreScreen(),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					                  GoRoute(
 | 
				
			||||||
 | 
					                    path: '/posts/search',
 | 
				
			||||||
 | 
					                    builder: (context, state) => const PostSearchScreen(),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                  GoRoute(
 | 
					                  GoRoute(
 | 
				
			||||||
                    path: '/posts/:id',
 | 
					                    path: '/posts/:id',
 | 
				
			||||||
                    builder: (context, state) {
 | 
					                    builder: (context, state) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										300
									
								
								lib/screens/about.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								lib/screens/about.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,300 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					import 'package:package_info_plus/package_info_plus.dart';
 | 
				
			||||||
 | 
					import 'package:url_launcher/url_launcher.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AboutScreen extends StatefulWidget {
 | 
				
			||||||
 | 
					  const AboutScreen({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<AboutScreen> createState() => _AboutScreenState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _AboutScreenState extends State<AboutScreen> {
 | 
				
			||||||
 | 
					  PackageInfo _packageInfo = PackageInfo(
 | 
				
			||||||
 | 
					    appName: 'Island',
 | 
				
			||||||
 | 
					    packageName: 'com.example.island',
 | 
				
			||||||
 | 
					    version: '1.0.0',
 | 
				
			||||||
 | 
					    buildNumber: '1',
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  bool _isLoading = true;
 | 
				
			||||||
 | 
					  String? _errorMessage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    _initPackageInfo();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _initPackageInfo() async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final info = await PackageInfo.fromPlatform();
 | 
				
			||||||
 | 
					      if (mounted) {
 | 
				
			||||||
 | 
					        setState(() {
 | 
				
			||||||
 | 
					          _packageInfo = info;
 | 
				
			||||||
 | 
					          _isLoading = false;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      if (mounted) {
 | 
				
			||||||
 | 
					        setState(() {
 | 
				
			||||||
 | 
					          _errorMessage = 'Failed to load package info: $e';
 | 
				
			||||||
 | 
					          _isLoading = false;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _launchURL(String url) async {
 | 
				
			||||||
 | 
					    final uri = Uri.parse(url);
 | 
				
			||||||
 | 
					    if (await canLaunchUrl(uri)) {
 | 
				
			||||||
 | 
					      await launchUrl(uri);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final theme = Theme.of(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(title: const Text('About'), elevation: 0),
 | 
				
			||||||
 | 
					      body:
 | 
				
			||||||
 | 
					          _isLoading
 | 
				
			||||||
 | 
					              ? const Center(child: CircularProgressIndicator())
 | 
				
			||||||
 | 
					              : _errorMessage != null
 | 
				
			||||||
 | 
					              ? Center(child: Text(_errorMessage!))
 | 
				
			||||||
 | 
					              : SingleChildScrollView(
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  crossAxisAlignment: CrossAxisAlignment.center,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    const SizedBox(height: 24),
 | 
				
			||||||
 | 
					                    // App Icon and Name
 | 
				
			||||||
 | 
					                    CircleAvatar(
 | 
				
			||||||
 | 
					                      radius: 50,
 | 
				
			||||||
 | 
					                      backgroundColor: theme.colorScheme.primary.withOpacity(
 | 
				
			||||||
 | 
					                        0.1,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      child: Image.asset(
 | 
				
			||||||
 | 
					                        'assets/icons/icon.png',
 | 
				
			||||||
 | 
					                        width: 56,
 | 
				
			||||||
 | 
					                        height: 56,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    const SizedBox(height: 16),
 | 
				
			||||||
 | 
					                    Text(
 | 
				
			||||||
 | 
					                      _packageInfo.appName,
 | 
				
			||||||
 | 
					                      style: theme.textTheme.headlineSmall?.copyWith(
 | 
				
			||||||
 | 
					                        fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    Text(
 | 
				
			||||||
 | 
					                      'Version ${_packageInfo.version} (${_packageInfo.buildNumber})',
 | 
				
			||||||
 | 
					                      style: theme.textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
 | 
					                        color: theme.textTheme.bodySmall?.color,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    const SizedBox(height: 32),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // App Info Card
 | 
				
			||||||
 | 
					                    _buildSection(
 | 
				
			||||||
 | 
					                      context,
 | 
				
			||||||
 | 
					                      title: 'App Information',
 | 
				
			||||||
 | 
					                      children: [
 | 
				
			||||||
 | 
					                        _buildInfoItem(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.info_outline,
 | 
				
			||||||
 | 
					                          label: 'Package Name',
 | 
				
			||||||
 | 
					                          value: _packageInfo.packageName,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        _buildInfoItem(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.update,
 | 
				
			||||||
 | 
					                          label: 'Version',
 | 
				
			||||||
 | 
					                          value: _packageInfo.version,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        _buildInfoItem(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.build,
 | 
				
			||||||
 | 
					                          label: 'Build Number',
 | 
				
			||||||
 | 
					                          value: _packageInfo.buildNumber,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const SizedBox(height: 16),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Links Card
 | 
				
			||||||
 | 
					                    _buildSection(
 | 
				
			||||||
 | 
					                      context,
 | 
				
			||||||
 | 
					                      title: 'Links',
 | 
				
			||||||
 | 
					                      children: [
 | 
				
			||||||
 | 
					                        _buildListTile(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.privacy_tip_outlined,
 | 
				
			||||||
 | 
					                          title: 'Privacy Policy',
 | 
				
			||||||
 | 
					                          onTap:
 | 
				
			||||||
 | 
					                              () => _launchURL(
 | 
				
			||||||
 | 
					                                'https://solsynth.dev/terms/privacy-policy',
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        _buildListTile(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.description_outlined,
 | 
				
			||||||
 | 
					                          title: 'Terms of Service',
 | 
				
			||||||
 | 
					                          onTap:
 | 
				
			||||||
 | 
					                              () => _launchURL(
 | 
				
			||||||
 | 
					                                'https://example.com/terms/basic-law',
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        _buildListTile(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.code,
 | 
				
			||||||
 | 
					                          title: 'Open Source Licenses',
 | 
				
			||||||
 | 
					                          onTap: () {
 | 
				
			||||||
 | 
					                            showLicensePage(
 | 
				
			||||||
 | 
					                              context: context,
 | 
				
			||||||
 | 
					                              applicationName: _packageInfo.appName,
 | 
				
			||||||
 | 
					                              applicationVersion:
 | 
				
			||||||
 | 
					                                  'Version ${_packageInfo.version}',
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const SizedBox(height: 16),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Developer Info
 | 
				
			||||||
 | 
					                    _buildSection(
 | 
				
			||||||
 | 
					                      context,
 | 
				
			||||||
 | 
					                      title: 'Developer',
 | 
				
			||||||
 | 
					                      children: [
 | 
				
			||||||
 | 
					                        _buildListTile(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.email_outlined,
 | 
				
			||||||
 | 
					                          title: 'Contact Us',
 | 
				
			||||||
 | 
					                          subtitle: 'lily@solsynth.dev',
 | 
				
			||||||
 | 
					                          onTap: () => _launchURL('mailto:lily@solsynth.dev'),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        _buildListTile(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          icon: Icons.copyright,
 | 
				
			||||||
 | 
					                          title: 'License',
 | 
				
			||||||
 | 
					                          subtitle:
 | 
				
			||||||
 | 
					                              'Copyright reserved © ${DateTime.now().year} Solsynth\nGNU Affero General Public License v3.0',
 | 
				
			||||||
 | 
					                          onTap:
 | 
				
			||||||
 | 
					                              () => _launchURL(
 | 
				
			||||||
 | 
					                                'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const SizedBox(height: 32),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Copyright
 | 
				
			||||||
 | 
					                    Padding(
 | 
				
			||||||
 | 
					                      padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
 | 
					                      child: Text(
 | 
				
			||||||
 | 
					                        '© ${DateTime.now().year} ${_packageInfo.appName}. All rights reserved.',
 | 
				
			||||||
 | 
					                        style: theme.textTheme.bodySmall,
 | 
				
			||||||
 | 
					                        textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildSection(
 | 
				
			||||||
 | 
					    BuildContext context, {
 | 
				
			||||||
 | 
					    required String title,
 | 
				
			||||||
 | 
					    required List<Widget> children,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    return Card(
 | 
				
			||||||
 | 
					      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
 | 
				
			||||||
 | 
					      child: Column(
 | 
				
			||||||
 | 
					        crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          Padding(
 | 
				
			||||||
 | 
					            padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
 | 
				
			||||||
 | 
					            child: Text(
 | 
				
			||||||
 | 
					              title,
 | 
				
			||||||
 | 
					              style: Theme.of(
 | 
				
			||||||
 | 
					                context,
 | 
				
			||||||
 | 
					              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          const Divider(height: 1),
 | 
				
			||||||
 | 
					          ...children,
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildInfoItem(
 | 
				
			||||||
 | 
					    BuildContext context, {
 | 
				
			||||||
 | 
					    required IconData icon,
 | 
				
			||||||
 | 
					    required String label,
 | 
				
			||||||
 | 
					    required String value,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    return Padding(
 | 
				
			||||||
 | 
					      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
 | 
				
			||||||
 | 
					      child: Row(
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          Icon(icon, size: 20, color: Theme.of(context).hintColor),
 | 
				
			||||||
 | 
					          const SizedBox(width: 16),
 | 
				
			||||||
 | 
					          Expanded(
 | 
				
			||||||
 | 
					            child: Column(
 | 
				
			||||||
 | 
					              crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                Text(label, style: Theme.of(context).textTheme.bodySmall),
 | 
				
			||||||
 | 
					                const SizedBox(height: 2),
 | 
				
			||||||
 | 
					                SelectableText(
 | 
				
			||||||
 | 
					                  value,
 | 
				
			||||||
 | 
					                  style: Theme.of(context).textTheme.bodyMedium,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          if (value.startsWith('http') || value.contains('@'))
 | 
				
			||||||
 | 
					            IconButton(
 | 
				
			||||||
 | 
					              icon: const Icon(Icons.copy, size: 16),
 | 
				
			||||||
 | 
					              onPressed: () {
 | 
				
			||||||
 | 
					                Clipboard.setData(ClipboardData(text: value));
 | 
				
			||||||
 | 
					                ScaffoldMessenger.of(context).showSnackBar(
 | 
				
			||||||
 | 
					                  const SnackBar(content: Text('Copied to clipboard')),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              padding: EdgeInsets.zero,
 | 
				
			||||||
 | 
					              constraints: const BoxConstraints(),
 | 
				
			||||||
 | 
					              tooltip: 'Copy to clipboard',
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildListTile(
 | 
				
			||||||
 | 
					    BuildContext context, {
 | 
				
			||||||
 | 
					    required IconData icon,
 | 
				
			||||||
 | 
					    required String title,
 | 
				
			||||||
 | 
					    String? subtitle,
 | 
				
			||||||
 | 
					    required VoidCallback onTap,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    return Column(
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        ListTile(
 | 
				
			||||||
 | 
					          leading: Icon(icon),
 | 
				
			||||||
 | 
					          title: Text(title),
 | 
				
			||||||
 | 
					          subtitle: subtitle != null ? Text(subtitle) : null,
 | 
				
			||||||
 | 
					          trailing: const Icon(Icons.chevron_right),
 | 
				
			||||||
 | 
					          onTap: onTap,
 | 
				
			||||||
 | 
					          contentPadding: const EdgeInsets.symmetric(horizontal: 16),
 | 
				
			||||||
 | 
					          minLeadingWidth: 24,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -281,6 +281,16 @@ class AccountScreen extends HookConsumerWidget {
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            const Divider(height: 1).padding(vertical: 8),
 | 
					            const Divider(height: 1).padding(vertical: 8),
 | 
				
			||||||
 | 
					            ListTile(
 | 
				
			||||||
 | 
					              minTileHeight: 48,
 | 
				
			||||||
 | 
					              leading: const Icon(Symbols.info),
 | 
				
			||||||
 | 
					              trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					              contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					              title: Text('about').tr(),
 | 
				
			||||||
 | 
					              onTap: () {
 | 
				
			||||||
 | 
					                context.push('/about');
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              minTileHeight: 48,
 | 
					              minTileHeight: 48,
 | 
				
			||||||
              leading: const Icon(Symbols.logout),
 | 
					              leading: const Icon(Symbols.logout),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -341,7 +341,10 @@ class UpdateProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  TextFormField(
 | 
					                  TextFormField(
 | 
				
			||||||
                    decoration: InputDecoration(labelText: 'bio'.tr()),
 | 
					                    decoration: InputDecoration(
 | 
				
			||||||
 | 
					                      labelText: 'bio'.tr(),
 | 
				
			||||||
 | 
					                      alignLabelWithHint: true,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                    maxLines: null,
 | 
					                    maxLines: null,
 | 
				
			||||||
                    minLines: 3,
 | 
					                    minLines: 3,
 | 
				
			||||||
                    controller: bioController,
 | 
					                    controller: bioController,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										105
									
								
								lib/screens/article_detail_screen.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								lib/screens/article_detail_screen.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
 | 
					import 'package:gap/gap.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/markdown.dart';
 | 
				
			||||||
 | 
					import 'package:url_launcher/url_launcher_string.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/article_detail.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/loading_indicator.dart';
 | 
				
			||||||
 | 
					import 'package:html2md/html2md.dart' as html2md;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ArticleDetailScreen extends ConsumerWidget {
 | 
				
			||||||
 | 
					  final String articleId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ArticleDetailScreen({super.key, required this.articleId});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    final articleAsync = ref.watch(articleDetailProvider(articleId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AppScaffold(
 | 
				
			||||||
 | 
					      body: articleAsync.when(
 | 
				
			||||||
 | 
					        data:
 | 
				
			||||||
 | 
					            (article) => AppScaffold(
 | 
				
			||||||
 | 
					              appBar: AppBar(
 | 
				
			||||||
 | 
					                leading: const BackButton(),
 | 
				
			||||||
 | 
					                title: Text(article.title),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              body: _ArticleDetailContent(article: article),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        loading: () => const Center(child: LoadingIndicator()),
 | 
				
			||||||
 | 
					        error:
 | 
				
			||||||
 | 
					            (error, stackTrace) =>
 | 
				
			||||||
 | 
					                Center(child: Text('Failed to load article: $error')),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _ArticleDetailContent extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  final SnWebArticle article;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const _ArticleDetailContent({required this.article});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    final markdownContent = useMemoized(
 | 
				
			||||||
 | 
					      () => html2md.convert(article.content ?? ''),
 | 
				
			||||||
 | 
					      [article],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return SingleChildScrollView(
 | 
				
			||||||
 | 
					      child: Column(
 | 
				
			||||||
 | 
					        crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          if (article.preview?.imageUrl != null)
 | 
				
			||||||
 | 
					            Image.network(
 | 
				
			||||||
 | 
					              article.preview!.imageUrl!,
 | 
				
			||||||
 | 
					              width: double.infinity,
 | 
				
			||||||
 | 
					              height: 200,
 | 
				
			||||||
 | 
					              fit: BoxFit.cover,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          Padding(
 | 
				
			||||||
 | 
					            padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
 | 
					            child: Column(
 | 
				
			||||||
 | 
					              crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                Text(
 | 
				
			||||||
 | 
					                  article.title,
 | 
				
			||||||
 | 
					                  style: Theme.of(context).textTheme.headlineSmall,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                const SizedBox(height: 8),
 | 
				
			||||||
 | 
					                if (article.feed?.title != null)
 | 
				
			||||||
 | 
					                  Text(
 | 
				
			||||||
 | 
					                    article.feed!.title,
 | 
				
			||||||
 | 
					                    style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
 | 
					                      color: Theme.of(context).colorScheme.onSurfaceVariant,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                const Divider(height: 32),
 | 
				
			||||||
 | 
					                if (article.content != null)
 | 
				
			||||||
 | 
					                  ...MarkdownTextContent.buildGenerator(
 | 
				
			||||||
 | 
					                    isDark: Theme.of(context).brightness == Brightness.dark,
 | 
				
			||||||
 | 
					                  ).buildWidgets(markdownContent)
 | 
				
			||||||
 | 
					                else if (article.preview?.description != null)
 | 
				
			||||||
 | 
					                  Text(article.preview!.description!),
 | 
				
			||||||
 | 
					                const Gap(24),
 | 
				
			||||||
 | 
					                FilledButton(
 | 
				
			||||||
 | 
					                  onPressed:
 | 
				
			||||||
 | 
					                      () => launchUrlString(
 | 
				
			||||||
 | 
					                        article.url,
 | 
				
			||||||
 | 
					                        mode: LaunchMode.externalApplication,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                  child: const Text('Read Full Article'),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                Gap(MediaQuery.of(context).padding.bottom),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -669,7 +669,10 @@ class EditChatScreen extends HookConsumerWidget {
 | 
				
			|||||||
                const SizedBox(height: 16),
 | 
					                const SizedBox(height: 16),
 | 
				
			||||||
                TextFormField(
 | 
					                TextFormField(
 | 
				
			||||||
                  controller: descriptionController,
 | 
					                  controller: descriptionController,
 | 
				
			||||||
                  decoration: const InputDecoration(labelText: 'Description'),
 | 
					                  decoration: const InputDecoration(
 | 
				
			||||||
 | 
					                    labelText: 'Description',
 | 
				
			||||||
 | 
					                    alignLabelWithHint: true,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                  minLines: 3,
 | 
					                  minLines: 3,
 | 
				
			||||||
                  maxLines: null,
 | 
					                  maxLines: null,
 | 
				
			||||||
                  onTapOutside:
 | 
					                  onTapOutside:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -370,9 +370,9 @@ class CreatorHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
                          ListTile(
 | 
					                          ListTile(
 | 
				
			||||||
                            minTileHeight: 48,
 | 
					                            minTileHeight: 48,
 | 
				
			||||||
                            title: Text('publisherMembers').tr(),
 | 
					                            title: Text('publisherMembers').tr(),
 | 
				
			||||||
                            trailing: Icon(Symbols.chevron_right),
 | 
					                            trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
                            leading: const Icon(Symbols.group),
 | 
					                            leading: const Icon(Symbols.group),
 | 
				
			||||||
                            contentPadding: EdgeInsets.symmetric(
 | 
					                            contentPadding: const EdgeInsets.symmetric(
 | 
				
			||||||
                              horizontal: 24,
 | 
					                              horizontal: 24,
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            onTap: () {
 | 
					                            onTap: () {
 | 
				
			||||||
@@ -387,6 +387,20 @@ class CreatorHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
                              );
 | 
					                              );
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
 | 
					                          ListTile(
 | 
				
			||||||
 | 
					                            minTileHeight: 48,
 | 
				
			||||||
 | 
					                            title: const Text('Web Feeds').tr(),
 | 
				
			||||||
 | 
					                            trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					                            leading: const Icon(Symbols.rss_feed),
 | 
				
			||||||
 | 
					                            contentPadding: const EdgeInsets.symmetric(
 | 
				
			||||||
 | 
					                              horizontal: 24,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            onTap: () {
 | 
				
			||||||
 | 
					                              context.push(
 | 
				
			||||||
 | 
					                                '/creators/${currentPublisher.value!.name}/feeds',
 | 
				
			||||||
 | 
					                              );
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
                          ExpansionTile(
 | 
					                          ExpansionTile(
 | 
				
			||||||
                            title: Text('publisherFeatures').tr(),
 | 
					                            title: Text('publisherFeatures').tr(),
 | 
				
			||||||
                            leading: const Icon(Symbols.flag),
 | 
					                            leading: const Icon(Symbols.flag),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -270,7 +270,10 @@ class EditPublisherScreen extends HookConsumerWidget {
 | 
				
			|||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    TextFormField(
 | 
					                    TextFormField(
 | 
				
			||||||
                      controller: bioController,
 | 
					                      controller: bioController,
 | 
				
			||||||
                      decoration: InputDecoration(labelText: 'bio'.tr()),
 | 
					                      decoration: InputDecoration(
 | 
				
			||||||
 | 
					                        labelText: 'bio'.tr(),
 | 
				
			||||||
 | 
					                        alignLabelWithHint: true,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
                      minLines: 3,
 | 
					                      minLines: 3,
 | 
				
			||||||
                      maxLines: null,
 | 
					                      maxLines: null,
 | 
				
			||||||
                      onTapOutside:
 | 
					                      onTapOutside:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,9 +71,7 @@ class SliverStickerPacksList extends HookConsumerWidget {
 | 
				
			|||||||
                subtitle: Text(sticker.description),
 | 
					                subtitle: Text(sticker.description),
 | 
				
			||||||
                trailing: const Icon(Symbols.chevron_right),
 | 
					                trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
                onTap: () {
 | 
					                onTap: () {
 | 
				
			||||||
                  context.push(
 | 
					                  context.push('/creators/$pubName/stickers/${sticker.id}');
 | 
				
			||||||
                    '/creators/$pubName/stickers/${sticker.id}',
 | 
					 | 
				
			||||||
                  );
 | 
					 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
@@ -230,6 +228,7 @@ class EditStickerPacksScreen extends HookConsumerWidget {
 | 
				
			|||||||
                  decoration: InputDecoration(
 | 
					                  decoration: InputDecoration(
 | 
				
			||||||
                    labelText: 'description'.tr(),
 | 
					                    labelText: 'description'.tr(),
 | 
				
			||||||
                    border: const UnderlineInputBorder(),
 | 
					                    border: const UnderlineInputBorder(),
 | 
				
			||||||
 | 
					                    alignLabelWithHint: true,
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  minLines: 3,
 | 
					                  minLines: 3,
 | 
				
			||||||
                  maxLines: null,
 | 
					                  maxLines: null,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										287
									
								
								lib/screens/creators/webfeed/webfeed_edit.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								lib/screens/creators/webfeed/webfeed_edit.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,287 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/alert.dart';
 | 
				
			||||||
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebFeedNewScreen extends StatelessWidget {
 | 
				
			||||||
 | 
					  final String pubName;
 | 
				
			||||||
 | 
					  const WebFeedNewScreen({super.key, required this.pubName});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return WebFeedEditScreen(pubName: pubName, feedId: null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebFeedEditScreen extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  final String pubName;
 | 
				
			||||||
 | 
					  final String? feedId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const WebFeedEditScreen({super.key, required this.pubName, this.feedId});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    final formKey = useMemoized(() => GlobalKey<FormState>());
 | 
				
			||||||
 | 
					    final titleController = useTextEditingController();
 | 
				
			||||||
 | 
					    final urlController = useTextEditingController();
 | 
				
			||||||
 | 
					    final descriptionController = useTextEditingController();
 | 
				
			||||||
 | 
					    final isLoading = useState(false);
 | 
				
			||||||
 | 
					    final isScrapEnabled = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final saveFeed = useCallback(() async {
 | 
				
			||||||
 | 
					      if (!formKey.currentState!.validate()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      isLoading.value = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        final feed = SnWebFeed(
 | 
				
			||||||
 | 
					          id: feedId ?? '',
 | 
				
			||||||
 | 
					          title: titleController.text,
 | 
				
			||||||
 | 
					          url: urlController.text,
 | 
				
			||||||
 | 
					          description: descriptionController.text,
 | 
				
			||||||
 | 
					          config: SnWebFeedConfig(scrapPage: isScrapEnabled.value),
 | 
				
			||||||
 | 
					          publisherId: pubName,
 | 
				
			||||||
 | 
					          createdAt: DateTime.now(),
 | 
				
			||||||
 | 
					          updatedAt: DateTime.now(),
 | 
				
			||||||
 | 
					          deletedAt: null,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await ref
 | 
				
			||||||
 | 
					            .read(
 | 
				
			||||||
 | 
					              webFeedNotifierProvider((
 | 
				
			||||||
 | 
					                pubName: pubName,
 | 
				
			||||||
 | 
					                feedId: feedId,
 | 
				
			||||||
 | 
					              )).notifier,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .saveFeed(feed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Refresh the feed list
 | 
				
			||||||
 | 
					        ref.invalidate(webFeedListProvider(pubName));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (context.mounted) {
 | 
				
			||||||
 | 
					          showSnackBar('Web feed saved successfully');
 | 
				
			||||||
 | 
					          context.pop();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        showErrorAlert(e);
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        isLoading.value = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, [pubName, feedId, isScrapEnabled.value, context]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final deleteFeed = useCallback(() async {
 | 
				
			||||||
 | 
					      final confirmed = await showConfirmAlert(
 | 
				
			||||||
 | 
					        'Are you sure you want to delete this web feed? This action cannot be undone.',
 | 
				
			||||||
 | 
					        'Delete Web Feed',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      if (confirmed != true) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      isLoading.value = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await ref
 | 
				
			||||||
 | 
					            .read(
 | 
				
			||||||
 | 
					              webFeedNotifierProvider((
 | 
				
			||||||
 | 
					                pubName: pubName,
 | 
				
			||||||
 | 
					                feedId: feedId!,
 | 
				
			||||||
 | 
					              )).notifier,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .deleteFeed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ref.invalidate(webFeedListProvider(pubName));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (context.mounted) {
 | 
				
			||||||
 | 
					          showSnackBar('Web feed deleted successfully');
 | 
				
			||||||
 | 
					          context.pop();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        showErrorAlert(e);
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        isLoading.value = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, [pubName, feedId, context, ref]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final feedAsync = ref.watch(
 | 
				
			||||||
 | 
					      webFeedNotifierProvider((pubName: pubName, feedId: feedId)),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return feedAsync.when(
 | 
				
			||||||
 | 
					      loading:
 | 
				
			||||||
 | 
					          () =>
 | 
				
			||||||
 | 
					              const Scaffold(body: Center(child: CircularProgressIndicator())),
 | 
				
			||||||
 | 
					      error:
 | 
				
			||||||
 | 
					          (error, stack) => Scaffold(
 | 
				
			||||||
 | 
					            appBar: AppBar(title: const Text('Error')),
 | 
				
			||||||
 | 
					            body: Center(child: Text('Error: $error')),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					      data: (feed) {
 | 
				
			||||||
 | 
					        // Initialize form fields if they're empty and we have a feed
 | 
				
			||||||
 | 
					        if (titleController.text.isEmpty) {
 | 
				
			||||||
 | 
					          titleController.text = feed.title;
 | 
				
			||||||
 | 
					          urlController.text = feed.url;
 | 
				
			||||||
 | 
					          descriptionController.text = feed.description ?? '';
 | 
				
			||||||
 | 
					          isScrapEnabled.value = feed.config.scrapPage;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return _buildForm(
 | 
				
			||||||
 | 
					          context,
 | 
				
			||||||
 | 
					          formKey: formKey,
 | 
				
			||||||
 | 
					          titleController: titleController,
 | 
				
			||||||
 | 
					          urlController: urlController,
 | 
				
			||||||
 | 
					          descriptionController: descriptionController,
 | 
				
			||||||
 | 
					          isScrapEnabled: isScrapEnabled.value,
 | 
				
			||||||
 | 
					          onScrapEnabledChanged: (value) => isScrapEnabled.value = value,
 | 
				
			||||||
 | 
					          onSave: saveFeed,
 | 
				
			||||||
 | 
					          onDelete: deleteFeed,
 | 
				
			||||||
 | 
					          isLoading: isLoading.value,
 | 
				
			||||||
 | 
					          ref: ref,
 | 
				
			||||||
 | 
					          hasFeedId: feedId != null,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildForm(
 | 
				
			||||||
 | 
					    BuildContext context, {
 | 
				
			||||||
 | 
					    required WidgetRef ref,
 | 
				
			||||||
 | 
					    required GlobalKey<FormState> formKey,
 | 
				
			||||||
 | 
					    required TextEditingController titleController,
 | 
				
			||||||
 | 
					    required TextEditingController urlController,
 | 
				
			||||||
 | 
					    required TextEditingController descriptionController,
 | 
				
			||||||
 | 
					    required bool isScrapEnabled,
 | 
				
			||||||
 | 
					    required ValueChanged<bool> onScrapEnabledChanged,
 | 
				
			||||||
 | 
					    required VoidCallback onSave,
 | 
				
			||||||
 | 
					    required VoidCallback onDelete,
 | 
				
			||||||
 | 
					    required bool isLoading,
 | 
				
			||||||
 | 
					    required bool hasFeedId,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    final scrapNow = useCallback(() async {
 | 
				
			||||||
 | 
					      showLoadingModal(context);
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await ref
 | 
				
			||||||
 | 
					            .read(
 | 
				
			||||||
 | 
					              webFeedNotifierProvider((
 | 
				
			||||||
 | 
					                pubName: pubName,
 | 
				
			||||||
 | 
					                feedId: feedId!,
 | 
				
			||||||
 | 
					              )).notifier,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .scrapFeed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (context.mounted) {
 | 
				
			||||||
 | 
					          showSnackBar('Feed scraping successfully.');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        showErrorAlert(e);
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        if (context.mounted) hideLoadingModal(context);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, [pubName, feedId, ref, context]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(
 | 
				
			||||||
 | 
					        title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'),
 | 
				
			||||||
 | 
					        actions: [
 | 
				
			||||||
 | 
					          if (hasFeedId)
 | 
				
			||||||
 | 
					            IconButton(
 | 
				
			||||||
 | 
					              icon: const Icon(Symbols.delete_forever),
 | 
				
			||||||
 | 
					              onPressed: isLoading ? null : onDelete,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          const SizedBox(width: 8),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      body: Form(
 | 
				
			||||||
 | 
					        key: formKey,
 | 
				
			||||||
 | 
					        child: SingleChildScrollView(
 | 
				
			||||||
 | 
					          child: Column(
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              TextFormField(
 | 
				
			||||||
 | 
					                controller: titleController,
 | 
				
			||||||
 | 
					                decoration: const InputDecoration(labelText: 'Title'),
 | 
				
			||||||
 | 
					                validator: (value) {
 | 
				
			||||||
 | 
					                  if (value == null || value.isEmpty) {
 | 
				
			||||||
 | 
					                    return 'Please enter a title';
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  return null;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                onTapOutside:
 | 
				
			||||||
 | 
					                    (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              const SizedBox(height: 16),
 | 
				
			||||||
 | 
					              TextFormField(
 | 
				
			||||||
 | 
					                controller: urlController,
 | 
				
			||||||
 | 
					                decoration: const InputDecoration(
 | 
				
			||||||
 | 
					                  labelText: 'URL',
 | 
				
			||||||
 | 
					                  hintText: 'https://example.com/feed',
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                keyboardType: TextInputType.url,
 | 
				
			||||||
 | 
					                validator: (value) {
 | 
				
			||||||
 | 
					                  if (value == null || value.isEmpty) {
 | 
				
			||||||
 | 
					                    return 'Please enter a URL';
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  final uri = Uri.tryParse(value);
 | 
				
			||||||
 | 
					                  if (uri == null || !uri.hasAbsolutePath) {
 | 
				
			||||||
 | 
					                    return 'Please enter a valid URL';
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  return null;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                onTapOutside:
 | 
				
			||||||
 | 
					                    (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              const SizedBox(height: 16),
 | 
				
			||||||
 | 
					              TextFormField(
 | 
				
			||||||
 | 
					                controller: descriptionController,
 | 
				
			||||||
 | 
					                decoration: const InputDecoration(
 | 
				
			||||||
 | 
					                  labelText: 'Description',
 | 
				
			||||||
 | 
					                  alignLabelWithHint: true,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                onTapOutside:
 | 
				
			||||||
 | 
					                    (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
				
			||||||
 | 
					                maxLines: 3,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              const SizedBox(height: 24),
 | 
				
			||||||
 | 
					              Card(
 | 
				
			||||||
 | 
					                margin: EdgeInsets.zero,
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    SwitchListTile(
 | 
				
			||||||
 | 
					                      title: const Text('Scrape web page for content'),
 | 
				
			||||||
 | 
					                      subtitle: const Text(
 | 
				
			||||||
 | 
					                        'When enabled, the system will attempt to extract full content from the web page',
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      shape: RoundedRectangleBorder(
 | 
				
			||||||
 | 
					                        borderRadius: BorderRadius.circular(8),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      value: isScrapEnabled,
 | 
				
			||||||
 | 
					                      onChanged: onScrapEnabledChanged,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              const SizedBox(height: 20),
 | 
				
			||||||
 | 
					              if (hasFeedId) ...[
 | 
				
			||||||
 | 
					                FilledButton.tonalIcon(
 | 
				
			||||||
 | 
					                  onPressed: isLoading ? null : scrapNow,
 | 
				
			||||||
 | 
					                  icon: const Icon(Symbols.refresh),
 | 
				
			||||||
 | 
					                  label: const Text('Scrape Now'),
 | 
				
			||||||
 | 
					                ).alignment(Alignment.centerRight),
 | 
				
			||||||
 | 
					                const SizedBox(height: 16),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					              FilledButton.icon(
 | 
				
			||||||
 | 
					                onPressed: isLoading ? null : onSave,
 | 
				
			||||||
 | 
					                icon: const Icon(Symbols.save),
 | 
				
			||||||
 | 
					                label: Text('saveChanges').tr(),
 | 
				
			||||||
 | 
					              ).alignment(Alignment.centerRight),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          ).padding(all: 20),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										78
									
								
								lib/screens/creators/webfeed/webfeed_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								lib/screens/creators/webfeed/webfeed_list.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/empty_state.dart';
 | 
				
			||||||
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebFeedListScreen extends ConsumerWidget {
 | 
				
			||||||
 | 
					  final String pubName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const WebFeedListScreen({super.key, required this.pubName});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    final feedsAsync = ref.watch(webFeedListProvider(pubName));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AppScaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(title: const Text('Web Feeds')),
 | 
				
			||||||
 | 
					      floatingActionButton: FloatingActionButton(
 | 
				
			||||||
 | 
					        child: const Icon(Symbols.add),
 | 
				
			||||||
 | 
					        onPressed: () {
 | 
				
			||||||
 | 
					          context.push('/creators/$pubName/feeds/new');
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      body: feedsAsync.when(
 | 
				
			||||||
 | 
					        data: (feeds) {
 | 
				
			||||||
 | 
					          if (feeds.isEmpty) {
 | 
				
			||||||
 | 
					            return EmptyState(
 | 
				
			||||||
 | 
					              icon: Symbols.rss_feed,
 | 
				
			||||||
 | 
					              title: 'No Web Feeds',
 | 
				
			||||||
 | 
					              description: 'Add a new web feed to get started',
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return RefreshIndicator(
 | 
				
			||||||
 | 
					            onRefresh: () => ref.refresh(webFeedListProvider(pubName).future),
 | 
				
			||||||
 | 
					            child: ListView.builder(
 | 
				
			||||||
 | 
					              padding: EdgeInsets.only(top: 8),
 | 
				
			||||||
 | 
					              itemCount: feeds.length,
 | 
				
			||||||
 | 
					              itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                final feed = feeds[index];
 | 
				
			||||||
 | 
					                return Card(
 | 
				
			||||||
 | 
					                  margin: const EdgeInsets.symmetric(
 | 
				
			||||||
 | 
					                    horizontal: 12,
 | 
				
			||||||
 | 
					                    vertical: 4,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  child: ListTile(
 | 
				
			||||||
 | 
					                    leading: const Icon(Symbols.rss_feed, size: 32),
 | 
				
			||||||
 | 
					                    shape: RoundedRectangleBorder(
 | 
				
			||||||
 | 
					                      borderRadius: BorderRadius.circular(8),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    title: Text(
 | 
				
			||||||
 | 
					                      feed.title,
 | 
				
			||||||
 | 
					                      style: Theme.of(context).textTheme.titleMedium,
 | 
				
			||||||
 | 
					                      maxLines: 1,
 | 
				
			||||||
 | 
					                      overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    subtitle: Text(
 | 
				
			||||||
 | 
					                      feed.url,
 | 
				
			||||||
 | 
					                      maxLines: 1,
 | 
				
			||||||
 | 
					                      overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					                    onTap: () {
 | 
				
			||||||
 | 
					                      context.push('/creators/$pubName/feeds/${feed.id}');
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        loading: () => const Center(child: CircularProgressIndicator()),
 | 
				
			||||||
 | 
					        error: (error, _) => Center(child: Text('Error: $error')),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -378,6 +378,7 @@ class EditAppScreen extends HookConsumerWidget {
 | 
				
			|||||||
                            controller: descriptionController,
 | 
					                            controller: descriptionController,
 | 
				
			||||||
                            decoration: InputDecoration(
 | 
					                            decoration: InputDecoration(
 | 
				
			||||||
                              labelText: 'description'.tr(),
 | 
					                              labelText: 'description'.tr(),
 | 
				
			||||||
 | 
					                              alignLabelWithHint: true,
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            maxLines: 3,
 | 
					                            maxLines: 3,
 | 
				
			||||||
                            onTapOutside:
 | 
					                            onTapOutside:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										142
									
								
								lib/screens/discovery/articles.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								lib/screens/discovery/articles.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/web_article_card.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part 'articles.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@riverpod
 | 
				
			||||||
 | 
					class ArticlesListNotifier extends _$ArticlesListNotifier
 | 
				
			||||||
 | 
					    with CursorPagingNotifierMixin<SnWebArticle> {
 | 
				
			||||||
 | 
					  static const int _pageSize = 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Map<String, dynamic> _params = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<CursorPagingData<SnWebArticle>> build({
 | 
				
			||||||
 | 
					    String? feedId,
 | 
				
			||||||
 | 
					    String? publisherId,
 | 
				
			||||||
 | 
					  }) async {
 | 
				
			||||||
 | 
					    _params = {
 | 
				
			||||||
 | 
					      if (feedId != null) 'feedId': feedId,
 | 
				
			||||||
 | 
					      if (publisherId != null) 'publisherId': publisherId,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    return fetch(cursor: null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<CursorPagingData<SnWebArticle>> fetch({
 | 
				
			||||||
 | 
					    required String? cursor,
 | 
				
			||||||
 | 
					  }) async {
 | 
				
			||||||
 | 
					    final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					    final offset = cursor == null ? 0 : int.parse(cursor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final queryParams = {'limit': _pageSize, 'offset': offset, ..._params};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final response = await client.get(
 | 
				
			||||||
 | 
					        '/feeds/articles',
 | 
				
			||||||
 | 
					        queryParameters: queryParams,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final List<dynamic> data = response.data;
 | 
				
			||||||
 | 
					      final articles =
 | 
				
			||||||
 | 
					          data
 | 
				
			||||||
 | 
					              .map(
 | 
				
			||||||
 | 
					                (json) => SnWebArticle.fromJson(json as Map<String, dynamic>),
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					              .toList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final total = int.tryParse(response.headers.value('X-Total') ?? '0') ?? 0;
 | 
				
			||||||
 | 
					      final hasMore = offset + articles.length < total;
 | 
				
			||||||
 | 
					      final nextCursor = hasMore ? (offset + articles.length).toString() : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return CursorPagingData(
 | 
				
			||||||
 | 
					        items: articles,
 | 
				
			||||||
 | 
					        hasMore: hasMore,
 | 
				
			||||||
 | 
					        nextCursor: nextCursor,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      debugPrint('Error fetching articles: $e');
 | 
				
			||||||
 | 
					      rethrow;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SliverArticlesList extends ConsumerWidget {
 | 
				
			||||||
 | 
					  final String? feedId;
 | 
				
			||||||
 | 
					  final String? publisherId;
 | 
				
			||||||
 | 
					  final Color? backgroundColor;
 | 
				
			||||||
 | 
					  final EdgeInsets? padding;
 | 
				
			||||||
 | 
					  final Function? onRefresh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const SliverArticlesList({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    this.feedId,
 | 
				
			||||||
 | 
					    this.publisherId,
 | 
				
			||||||
 | 
					    this.backgroundColor,
 | 
				
			||||||
 | 
					    this.padding,
 | 
				
			||||||
 | 
					    this.onRefresh,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    return PagingHelperSliverView(
 | 
				
			||||||
 | 
					      provider: articlesListNotifierProvider(
 | 
				
			||||||
 | 
					        feedId: feedId,
 | 
				
			||||||
 | 
					        publisherId: publisherId,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      futureRefreshable:
 | 
				
			||||||
 | 
					          articlesListNotifierProvider(
 | 
				
			||||||
 | 
					            feedId: feedId,
 | 
				
			||||||
 | 
					            publisherId: publisherId,
 | 
				
			||||||
 | 
					          ).future,
 | 
				
			||||||
 | 
					      notifierRefreshable:
 | 
				
			||||||
 | 
					          articlesListNotifierProvider(
 | 
				
			||||||
 | 
					            feedId: feedId,
 | 
				
			||||||
 | 
					            publisherId: publisherId,
 | 
				
			||||||
 | 
					          ).notifier,
 | 
				
			||||||
 | 
					      contentBuilder:
 | 
				
			||||||
 | 
					          (data, widgetCount, endItemView) => SliverList.builder(
 | 
				
			||||||
 | 
					            itemCount: widgetCount,
 | 
				
			||||||
 | 
					            itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					              if (index == widgetCount - 1) {
 | 
				
			||||||
 | 
					                return endItemView;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              final article = data.items[index];
 | 
				
			||||||
 | 
					              return WebArticleCard(article: article, showDetails: true);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ArticlesScreen extends ConsumerWidget {
 | 
				
			||||||
 | 
					  final String? feedId;
 | 
				
			||||||
 | 
					  final String? publisherId;
 | 
				
			||||||
 | 
					  final String? title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ArticlesScreen({super.key, this.feedId, this.publisherId, this.title});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(title: Text(title ?? 'Articles')),
 | 
				
			||||||
 | 
					      body: CustomScrollView(
 | 
				
			||||||
 | 
					        slivers: [
 | 
				
			||||||
 | 
					          SliverPadding(
 | 
				
			||||||
 | 
					            padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
 | 
				
			||||||
 | 
					            sliver: SliverArticlesList(
 | 
				
			||||||
 | 
					              feedId: feedId,
 | 
				
			||||||
 | 
					              publisherId: publisherId,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										206
									
								
								lib/screens/discovery/articles.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								lib/screens/discovery/articles.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,206 @@
 | 
				
			|||||||
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					part of 'articles.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					// RiverpodGenerator
 | 
				
			||||||
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String _$articlesListNotifierHash() =>
 | 
				
			||||||
 | 
					    r'924f2344c3bbf0ff7b92fe69e88d3b64a534b538';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Copied from Dart SDK
 | 
				
			||||||
 | 
					class _SystemHash {
 | 
				
			||||||
 | 
					  _SystemHash._();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static int combine(int hash, int value) {
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + value);
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
 | 
				
			||||||
 | 
					    return hash ^ (hash >> 6);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static int finish(int hash) {
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
 | 
				
			||||||
 | 
					    // ignore: parameter_assignments
 | 
				
			||||||
 | 
					    hash = hash ^ (hash >> 11);
 | 
				
			||||||
 | 
					    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class _$ArticlesListNotifier
 | 
				
			||||||
 | 
					    extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnWebArticle>> {
 | 
				
			||||||
 | 
					  late final String? feedId;
 | 
				
			||||||
 | 
					  late final String? publisherId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  FutureOr<CursorPagingData<SnWebArticle>> build({
 | 
				
			||||||
 | 
					    String? feedId,
 | 
				
			||||||
 | 
					    String? publisherId,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [ArticlesListNotifier].
 | 
				
			||||||
 | 
					@ProviderFor(ArticlesListNotifier)
 | 
				
			||||||
 | 
					const articlesListNotifierProvider = ArticlesListNotifierFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [ArticlesListNotifier].
 | 
				
			||||||
 | 
					class ArticlesListNotifierFamily
 | 
				
			||||||
 | 
					    extends Family<AsyncValue<CursorPagingData<SnWebArticle>>> {
 | 
				
			||||||
 | 
					  /// See also [ArticlesListNotifier].
 | 
				
			||||||
 | 
					  const ArticlesListNotifierFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// See also [ArticlesListNotifier].
 | 
				
			||||||
 | 
					  ArticlesListNotifierProvider call({String? feedId, String? publisherId}) {
 | 
				
			||||||
 | 
					    return ArticlesListNotifierProvider(
 | 
				
			||||||
 | 
					      feedId: feedId,
 | 
				
			||||||
 | 
					      publisherId: publisherId,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  ArticlesListNotifierProvider getProviderOverride(
 | 
				
			||||||
 | 
					    covariant ArticlesListNotifierProvider provider,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return call(feedId: provider.feedId, publisherId: provider.publisherId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _dependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get dependencies => _dependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
 | 
				
			||||||
 | 
					      _allTransitiveDependencies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String? get name => r'articlesListNotifierProvider';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// See also [ArticlesListNotifier].
 | 
				
			||||||
 | 
					class ArticlesListNotifierProvider
 | 
				
			||||||
 | 
					    extends
 | 
				
			||||||
 | 
					        AutoDisposeAsyncNotifierProviderImpl<
 | 
				
			||||||
 | 
					          ArticlesListNotifier,
 | 
				
			||||||
 | 
					          CursorPagingData<SnWebArticle>
 | 
				
			||||||
 | 
					        > {
 | 
				
			||||||
 | 
					  /// See also [ArticlesListNotifier].
 | 
				
			||||||
 | 
					  ArticlesListNotifierProvider({String? feedId, String? publisherId})
 | 
				
			||||||
 | 
					    : this._internal(
 | 
				
			||||||
 | 
					        () =>
 | 
				
			||||||
 | 
					            ArticlesListNotifier()
 | 
				
			||||||
 | 
					              ..feedId = feedId
 | 
				
			||||||
 | 
					              ..publisherId = publisherId,
 | 
				
			||||||
 | 
					        from: articlesListNotifierProvider,
 | 
				
			||||||
 | 
					        name: r'articlesListNotifierProvider',
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash:
 | 
				
			||||||
 | 
					            const bool.fromEnvironment('dart.vm.product')
 | 
				
			||||||
 | 
					                ? null
 | 
				
			||||||
 | 
					                : _$articlesListNotifierHash,
 | 
				
			||||||
 | 
					        dependencies: ArticlesListNotifierFamily._dependencies,
 | 
				
			||||||
 | 
					        allTransitiveDependencies:
 | 
				
			||||||
 | 
					            ArticlesListNotifierFamily._allTransitiveDependencies,
 | 
				
			||||||
 | 
					        feedId: feedId,
 | 
				
			||||||
 | 
					        publisherId: publisherId,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ArticlesListNotifierProvider._internal(
 | 
				
			||||||
 | 
					    super._createNotifier, {
 | 
				
			||||||
 | 
					    required super.name,
 | 
				
			||||||
 | 
					    required super.dependencies,
 | 
				
			||||||
 | 
					    required super.allTransitiveDependencies,
 | 
				
			||||||
 | 
					    required super.debugGetCreateSourceHash,
 | 
				
			||||||
 | 
					    required super.from,
 | 
				
			||||||
 | 
					    required this.feedId,
 | 
				
			||||||
 | 
					    required this.publisherId,
 | 
				
			||||||
 | 
					  }) : super.internal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String? feedId;
 | 
				
			||||||
 | 
					  final String? publisherId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  FutureOr<CursorPagingData<SnWebArticle>> runNotifierBuild(
 | 
				
			||||||
 | 
					    covariant ArticlesListNotifier notifier,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return notifier.build(feedId: feedId, publisherId: publisherId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Override overrideWith(ArticlesListNotifier Function() create) {
 | 
				
			||||||
 | 
					    return ProviderOverride(
 | 
				
			||||||
 | 
					      origin: this,
 | 
				
			||||||
 | 
					      override: ArticlesListNotifierProvider._internal(
 | 
				
			||||||
 | 
					        () =>
 | 
				
			||||||
 | 
					            create()
 | 
				
			||||||
 | 
					              ..feedId = feedId
 | 
				
			||||||
 | 
					              ..publisherId = publisherId,
 | 
				
			||||||
 | 
					        from: from,
 | 
				
			||||||
 | 
					        name: null,
 | 
				
			||||||
 | 
					        dependencies: null,
 | 
				
			||||||
 | 
					        allTransitiveDependencies: null,
 | 
				
			||||||
 | 
					        debugGetCreateSourceHash: null,
 | 
				
			||||||
 | 
					        feedId: feedId,
 | 
				
			||||||
 | 
					        publisherId: publisherId,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  AutoDisposeAsyncNotifierProviderElement<
 | 
				
			||||||
 | 
					    ArticlesListNotifier,
 | 
				
			||||||
 | 
					    CursorPagingData<SnWebArticle>
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					  createElement() {
 | 
				
			||||||
 | 
					    return _ArticlesListNotifierProviderElement(this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool operator ==(Object other) {
 | 
				
			||||||
 | 
					    return other is ArticlesListNotifierProvider &&
 | 
				
			||||||
 | 
					        other.feedId == feedId &&
 | 
				
			||||||
 | 
					        other.publisherId == publisherId;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  int get hashCode {
 | 
				
			||||||
 | 
					    var hash = _SystemHash.combine(0, runtimeType.hashCode);
 | 
				
			||||||
 | 
					    hash = _SystemHash.combine(hash, feedId.hashCode);
 | 
				
			||||||
 | 
					    hash = _SystemHash.combine(hash, publisherId.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return _SystemHash.finish(hash);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
 | 
					// ignore: unused_element
 | 
				
			||||||
 | 
					mixin ArticlesListNotifierRef
 | 
				
			||||||
 | 
					    on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnWebArticle>> {
 | 
				
			||||||
 | 
					  /// The parameter `feedId` of this provider.
 | 
				
			||||||
 | 
					  String? get feedId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The parameter `publisherId` of this provider.
 | 
				
			||||||
 | 
					  String? get publisherId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _ArticlesListNotifierProviderElement
 | 
				
			||||||
 | 
					    extends
 | 
				
			||||||
 | 
					        AutoDisposeAsyncNotifierProviderElement<
 | 
				
			||||||
 | 
					          ArticlesListNotifier,
 | 
				
			||||||
 | 
					          CursorPagingData<SnWebArticle>
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					    with ArticlesListNotifierRef {
 | 
				
			||||||
 | 
					  _ArticlesListNotifierProviderElement(super.provider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String? get feedId => (origin as ArticlesListNotifierProvider).feedId;
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String? get publisherId =>
 | 
				
			||||||
 | 
					      (origin as ArticlesListNotifierProvider).publisherId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
@@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			|||||||
import 'package:island/models/activity.dart';
 | 
					import 'package:island/models/activity.dart';
 | 
				
			||||||
import 'package:island/models/publisher.dart';
 | 
					import 'package:island/models/publisher.dart';
 | 
				
			||||||
import 'package:island/models/realm.dart';
 | 
					import 'package:island/models/realm.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/webfeed.dart';
 | 
				
			||||||
import 'package:island/pods/userinfo.dart';
 | 
					import 'package:island/pods/userinfo.dart';
 | 
				
			||||||
import 'package:island/services/responsive.dart';
 | 
					import 'package:island/services/responsive.dart';
 | 
				
			||||||
import 'package:island/widgets/app_scaffold.dart';
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
@@ -21,6 +22,7 @@ import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
				
			|||||||
import 'package:island/pods/network.dart';
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
import 'package:island/widgets/realm/realm_card.dart';
 | 
					import 'package:island/widgets/realm/realm_card.dart';
 | 
				
			||||||
import 'package:island/widgets/publisher/publisher_card.dart';
 | 
					import 'package:island/widgets/publisher/publisher_card.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/web_article_card.dart';
 | 
				
			||||||
import 'package:styled_widget/styled_widget.dart';
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part 'explore.g.dart';
 | 
					part 'explore.g.dart';
 | 
				
			||||||
@@ -91,37 +93,69 @@ class ExploreScreen extends HookConsumerWidget {
 | 
				
			|||||||
      extendBody: false, // Prevent conflicts with tabs navigation
 | 
					      extendBody: false, // Prevent conflicts with tabs navigation
 | 
				
			||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
        toolbarHeight: 0,
 | 
					        toolbarHeight: 0,
 | 
				
			||||||
        bottom: TabBar(
 | 
					        bottom: PreferredSize(
 | 
				
			||||||
          controller: tabController,
 | 
					          preferredSize: const Size.fromHeight(48),
 | 
				
			||||||
          tabs: [
 | 
					          child: Row(
 | 
				
			||||||
            Tab(
 | 
					            children: [
 | 
				
			||||||
              child: Text(
 | 
					              Expanded(
 | 
				
			||||||
                'explore'.tr(),
 | 
					                child: TabBar(
 | 
				
			||||||
                textAlign: TextAlign.center,
 | 
					                  controller: tabController,
 | 
				
			||||||
                style: TextStyle(
 | 
					                  tabAlignment: TabAlignment.start,
 | 
				
			||||||
                  color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
					                  isScrollable: true,
 | 
				
			||||||
 | 
					                  tabs: [
 | 
				
			||||||
 | 
					                    Tab(
 | 
				
			||||||
 | 
					                      icon: Tooltip(
 | 
				
			||||||
 | 
					                        message: 'explore'.tr(),
 | 
				
			||||||
 | 
					                        child: Icon(
 | 
				
			||||||
 | 
					                          Symbols.explore,
 | 
				
			||||||
 | 
					                          color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    Tab(
 | 
				
			||||||
 | 
					                      icon: Tooltip(
 | 
				
			||||||
 | 
					                        message: 'exploreFilterSubscriptions'.tr(),
 | 
				
			||||||
 | 
					                        child: Icon(
 | 
				
			||||||
 | 
					                          Symbols.subscriptions,
 | 
				
			||||||
 | 
					                          color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    Tab(
 | 
				
			||||||
 | 
					                      icon: Tooltip(
 | 
				
			||||||
 | 
					                        message: 'exploreFilterFriends'.tr(),
 | 
				
			||||||
 | 
					                        child: Icon(
 | 
				
			||||||
 | 
					                          Symbols.people,
 | 
				
			||||||
 | 
					                          color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					              Spacer(),
 | 
				
			||||||
            Tab(
 | 
					              IconButton(
 | 
				
			||||||
              child: Text(
 | 
					                onPressed: () {
 | 
				
			||||||
                'exploreFilterSubscriptions'.tr(),
 | 
					                  context.push('/feeds/articles');
 | 
				
			||||||
                textAlign: TextAlign.center,
 | 
					                },
 | 
				
			||||||
                style: TextStyle(
 | 
					                icon: Icon(
 | 
				
			||||||
 | 
					                  Symbols.auto_stories,
 | 
				
			||||||
                  color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
					                  color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
					                tooltip: 'webArticlesStand'.tr(),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					              IconButton(
 | 
				
			||||||
            Tab(
 | 
					                onPressed: () {
 | 
				
			||||||
              child: Text(
 | 
					                  context.push('/posts/search');
 | 
				
			||||||
                'exploreFilterFriends'.tr(),
 | 
					                },
 | 
				
			||||||
                textAlign: TextAlign.center,
 | 
					                icon: Icon(
 | 
				
			||||||
                style: TextStyle(
 | 
					                  Symbols.search,
 | 
				
			||||||
                  color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
					                  color: Theme.of(context).appBarTheme.foregroundColor!,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
					                tooltip: 'search'.tr(),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ],
 | 
				
			||||||
          ],
 | 
					          ).padding(horizontal: 8),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      floatingActionButton: FloatingActionButton(
 | 
					      floatingActionButton: FloatingActionButton(
 | 
				
			||||||
@@ -196,6 +230,7 @@ class _DiscoveryActivityItem extends StatelessWidget {
 | 
				
			|||||||
              (switch (type) {
 | 
					              (switch (type) {
 | 
				
			||||||
                'realm' => 'discoverRealms',
 | 
					                'realm' => 'discoverRealms',
 | 
				
			||||||
                'publisher' => 'discoverPublishers',
 | 
					                'publisher' => 'discoverPublishers',
 | 
				
			||||||
 | 
					                'article' => 'discoverWebArticles',
 | 
				
			||||||
                _ => 'unknown',
 | 
					                _ => 'unknown',
 | 
				
			||||||
              }).tr(),
 | 
					              }).tr(),
 | 
				
			||||||
              style: Theme.of(context).textTheme.titleMedium,
 | 
					              style: Theme.of(context).textTheme.titleMedium,
 | 
				
			||||||
@@ -221,6 +256,11 @@ class _DiscoveryActivityItem extends StatelessWidget {
 | 
				
			|||||||
                    publisher: SnPublisher.fromJson(item['data']),
 | 
					                    publisher: SnPublisher.fromJson(item['data']),
 | 
				
			||||||
                    maxWidth: 280,
 | 
					                    maxWidth: 280,
 | 
				
			||||||
                  );
 | 
					                  );
 | 
				
			||||||
 | 
					                case 'article':
 | 
				
			||||||
 | 
					                  return WebArticleCard(
 | 
				
			||||||
 | 
					                    article: SnWebArticle.fromJson(item['data']),
 | 
				
			||||||
 | 
					                    maxWidth: 280,
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
                default:
 | 
					                default:
 | 
				
			||||||
                  return Placeholder();
 | 
					                  return Placeholder();
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
@@ -342,7 +382,7 @@ class ActivityListNotifier extends _$ActivityListNotifier
 | 
				
			|||||||
      if (cursor != null) 'cursor': cursor,
 | 
					      if (cursor != null) 'cursor': cursor,
 | 
				
			||||||
      'take': take,
 | 
					      'take': take,
 | 
				
			||||||
      if (filter != null) 'filter': filter,
 | 
					      if (filter != null) 'filter': filter,
 | 
				
			||||||
      if (kDebugMode) 'debugInclude': 'realms,publishers',
 | 
					      if (kDebugMode) 'debugInclude': 'realms,publishers,articles',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final response = await client.get(
 | 
					    final response = await client.get(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ part of 'explore.dart';
 | 
				
			|||||||
// **************************************************************************
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
String _$activityListNotifierHash() =>
 | 
					String _$activityListNotifierHash() =>
 | 
				
			||||||
    r'57e9dcec944a9f88f8508b69fc91342592f5b349';
 | 
					    r'98b62fb9b958023d2c9e320af7ec1f1244836f49';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Copied from Dart SDK
 | 
					/// Copied from Dart SDK
 | 
				
			||||||
class _SystemHash {
 | 
					class _SystemHash {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ import 'package:island/widgets/content/cloud_files.dart';
 | 
				
			|||||||
import 'package:island/widgets/post/compose_shared.dart';
 | 
					import 'package:island/widgets/post/compose_shared.dart';
 | 
				
			||||||
import 'package:island/widgets/post/post_item.dart';
 | 
					import 'package:island/widgets/post/post_item.dart';
 | 
				
			||||||
import 'package:island/widgets/post/publishers_modal.dart';
 | 
					import 'package:island/widgets/post/publishers_modal.dart';
 | 
				
			||||||
import 'package:island/screens/posts/detail.dart';
 | 
					import 'package:island/screens/posts/post_detail.dart';
 | 
				
			||||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
 | 
					import 'package:island/widgets/post/compose_settings_sheet.dart';
 | 
				
			||||||
import 'package:island/services/compose_storage_db.dart';
 | 
					import 'package:island/services/compose_storage_db.dart';
 | 
				
			||||||
import 'package:island/widgets/post/draft_manager.dart';
 | 
					import 'package:island/widgets/post/draft_manager.dart';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ import 'package:island/models/post.dart';
 | 
				
			|||||||
import 'package:island/screens/creators/publishers.dart';
 | 
					import 'package:island/screens/creators/publishers.dart';
 | 
				
			||||||
import 'package:island/services/responsive.dart';
 | 
					import 'package:island/services/responsive.dart';
 | 
				
			||||||
import 'package:island/widgets/app_scaffold.dart';
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
import 'package:island/screens/posts/detail.dart';
 | 
					import 'package:island/screens/posts/post_detail.dart';
 | 
				
			||||||
import 'package:island/widgets/content/attachment_preview.dart';
 | 
					import 'package:island/widgets/content/attachment_preview.dart';
 | 
				
			||||||
import 'package:island/widgets/content/cloud_files.dart';
 | 
					import 'package:island/widgets/content/cloud_files.dart';
 | 
				
			||||||
import 'package:island/widgets/content/markdown.dart';
 | 
					import 'package:island/widgets/content/markdown.dart';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,10 +9,11 @@ import 'package:island/widgets/app_scaffold.dart';
 | 
				
			|||||||
import 'package:island/widgets/post/post_item.dart';
 | 
					import 'package:island/widgets/post/post_item.dart';
 | 
				
			||||||
import 'package:island/widgets/post/post_quick_reply.dart';
 | 
					import 'package:island/widgets/post/post_quick_reply.dart';
 | 
				
			||||||
import 'package:island/widgets/post/post_replies.dart';
 | 
					import 'package:island/widgets/post/post_replies.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
import 'package:styled_widget/styled_widget.dart';
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part 'detail.g.dart';
 | 
					part 'post_detail.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@riverpod
 | 
					@riverpod
 | 
				
			||||||
Future<SnPost?> post(Ref ref, String id) async {
 | 
					Future<SnPost?> post(Ref ref, String id) async {
 | 
				
			||||||
@@ -21,20 +22,43 @@ Future<SnPost?> post(Ref ref, String id) async {
 | 
				
			|||||||
  return SnPost.fromJson(resp.data);
 | 
					  return SnPost.fromJson(resp.data);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final postStateProvider = StateNotifierProvider.family<PostState, AsyncValue<SnPost?>, String>(
 | 
				
			||||||
 | 
					  (ref, id) => PostState(ref, id),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PostState extends StateNotifier<AsyncValue<SnPost?>> {
 | 
				
			||||||
 | 
					  final Ref _ref;
 | 
				
			||||||
 | 
					  final String _id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PostState(this._ref, this._id) : super(const AsyncValue.loading()) {
 | 
				
			||||||
 | 
					    // Initialize with the initial post data
 | 
				
			||||||
 | 
					    _ref.listen<AsyncValue<SnPost?>>(
 | 
				
			||||||
 | 
					      postProvider(_id),
 | 
				
			||||||
 | 
					      (_, next) => state = next,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void updatePost(SnPost? newPost) {
 | 
				
			||||||
 | 
					    if (newPost != null) {
 | 
				
			||||||
 | 
					      state = AsyncData(newPost);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PostDetailScreen extends HookConsumerWidget {
 | 
					class PostDetailScreen extends HookConsumerWidget {
 | 
				
			||||||
  final String id;
 | 
					  final String id;
 | 
				
			||||||
  const PostDetailScreen({super.key, required this.id});
 | 
					  const PostDetailScreen({super.key, required this.id});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
    final post = ref.watch(postProvider(id));
 | 
					    final postState = ref.watch(postStateProvider(id));
 | 
				
			||||||
    final user = ref.watch(userInfoProvider);
 | 
					    final user = ref.watch(userInfoProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final isWide = isWideScreen(context);
 | 
					    final isWide = isWideScreen(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return AppScaffold(
 | 
					    return AppScaffold(
 | 
				
			||||||
      appBar: AppBar(title: const Text('Post')),
 | 
					      appBar: AppBar(title: const Text('Post')),
 | 
				
			||||||
      body: post.when(
 | 
					      body: postState.when(
 | 
				
			||||||
        data: (post) {
 | 
					        data: (post) {
 | 
				
			||||||
          return Stack(
 | 
					          return Stack(
 | 
				
			||||||
            fit: StackFit.expand,
 | 
					            fit: StackFit.expand,
 | 
				
			||||||
@@ -49,6 +73,10 @@ class PostDetailScreen extends HookConsumerWidget {
 | 
				
			|||||||
                          isOpenable: false,
 | 
					                          isOpenable: false,
 | 
				
			||||||
                          isFullPost: true,
 | 
					                          isFullPost: true,
 | 
				
			||||||
                          backgroundColor: isWide ? Colors.transparent : null,
 | 
					                          backgroundColor: isWide ? Colors.transparent : null,
 | 
				
			||||||
 | 
					                          onUpdate: (newItem) {
 | 
				
			||||||
 | 
					                            // Update the local state with the new post data
 | 
				
			||||||
 | 
					                            ref.read(postStateProvider(id).notifier).updatePost(newItem);
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                        const Divider(height: 1),
 | 
					                        const Divider(height: 1),
 | 
				
			||||||
                      ],
 | 
					                      ],
 | 
				
			||||||
@@ -65,11 +93,15 @@ class PostDetailScreen extends HookConsumerWidget {
 | 
				
			|||||||
                  right: 0,
 | 
					                  right: 0,
 | 
				
			||||||
                  child: Material(
 | 
					                  child: Material(
 | 
				
			||||||
                    elevation: 2,
 | 
					                    elevation: 2,
 | 
				
			||||||
                    child: PostQuickReply(
 | 
					                    child: postState.when(
 | 
				
			||||||
                      parent: post,
 | 
					                      data: (post) => PostQuickReply(
 | 
				
			||||||
                      onPosted: () {
 | 
					                        parent: post!,
 | 
				
			||||||
                        ref.invalidate(postRepliesNotifierProvider(id));
 | 
					                        onPosted: () {
 | 
				
			||||||
                      },
 | 
					                          ref.invalidate(postRepliesNotifierProvider(id));
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      loading: () => const SizedBox.shrink(),
 | 
				
			||||||
 | 
					                      error: (_, __) => const SizedBox.shrink(),
 | 
				
			||||||
                    ).padding(
 | 
					                    ).padding(
 | 
				
			||||||
                      bottom: MediaQuery.of(context).padding.bottom + 16,
 | 
					                      bottom: MediaQuery.of(context).padding.bottom + 16,
 | 
				
			||||||
                      top: 16,
 | 
					                      top: 16,
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part of 'detail.dart';
 | 
					part of 'post_detail.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// **************************************************************************
 | 
					// **************************************************************************
 | 
				
			||||||
// RiverpodGenerator
 | 
					// RiverpodGenerator
 | 
				
			||||||
							
								
								
									
										165
									
								
								lib/screens/posts/post_search.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								lib/screens/posts/post_search.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/post.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/post/post_item.dart';
 | 
				
			||||||
 | 
					import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final postSearchNotifierProvider = StateNotifierProvider.autoDispose<
 | 
				
			||||||
 | 
					  PostSearchNotifier,
 | 
				
			||||||
 | 
					  AsyncValue<CursorPagingData<SnPost>>
 | 
				
			||||||
 | 
					>((ref) => PostSearchNotifier(ref));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PostSearchNotifier
 | 
				
			||||||
 | 
					    extends StateNotifier<AsyncValue<CursorPagingData<SnPost>>> {
 | 
				
			||||||
 | 
					  final AutoDisposeRef ref;
 | 
				
			||||||
 | 
					  static const int _pageSize = 20;
 | 
				
			||||||
 | 
					  String _currentQuery = '';
 | 
				
			||||||
 | 
					  bool _isLoading = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PostSearchNotifier(this.ref) : super(const AsyncValue.loading()) {
 | 
				
			||||||
 | 
					    state = const AsyncValue.data(
 | 
				
			||||||
 | 
					      CursorPagingData(items: [], hasMore: false, nextCursor: null),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> search(String query) async {
 | 
				
			||||||
 | 
					    if (_isLoading) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _currentQuery = query.trim();
 | 
				
			||||||
 | 
					    if (_currentQuery.isEmpty) {
 | 
				
			||||||
 | 
					      state = AsyncValue.data(
 | 
				
			||||||
 | 
					        CursorPagingData(items: [], hasMore: false, nextCursor: null),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await fetch(cursor: null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> fetch({String? cursor}) async {
 | 
				
			||||||
 | 
					    if (_isLoading) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _isLoading = true;
 | 
				
			||||||
 | 
					    state = const AsyncValue.loading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final client = ref.read(apiClientProvider);
 | 
				
			||||||
 | 
					      final offset = cursor == null ? 0 : int.parse(cursor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final response = await client.get(
 | 
				
			||||||
 | 
					        '/posts/search',
 | 
				
			||||||
 | 
					        queryParameters: {
 | 
				
			||||||
 | 
					          'query': _currentQuery,
 | 
				
			||||||
 | 
					          'offset': offset,
 | 
				
			||||||
 | 
					          'take': _pageSize,
 | 
				
			||||||
 | 
					          'useVector': true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final data = response.data as List;
 | 
				
			||||||
 | 
					      final posts = data.map((json) => SnPost.fromJson(json)).toList();
 | 
				
			||||||
 | 
					      final hasMore = posts.length == _pageSize;
 | 
				
			||||||
 | 
					      final nextCursor = hasMore ? (offset + posts.length).toString() : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      state = AsyncValue.data(
 | 
				
			||||||
 | 
					        CursorPagingData(
 | 
				
			||||||
 | 
					          items: posts,
 | 
				
			||||||
 | 
					          hasMore: hasMore,
 | 
				
			||||||
 | 
					          nextCursor: nextCursor,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } catch (e, stack) {
 | 
				
			||||||
 | 
					      state = AsyncValue.error(e, stack);
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      _isLoading = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PostSearchScreen extends ConsumerStatefulWidget {
 | 
				
			||||||
 | 
					  const PostSearchScreen({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  ConsumerState<PostSearchScreen> createState() => _PostSearchScreenState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
 | 
				
			||||||
 | 
					  final _searchController = TextEditingController();
 | 
				
			||||||
 | 
					  final _debounce = Duration(milliseconds: 500);
 | 
				
			||||||
 | 
					  Timer? _debounceTimer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    _searchController.dispose();
 | 
				
			||||||
 | 
					    _debounceTimer?.cancel();
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onSearchChanged(String query) {
 | 
				
			||||||
 | 
					    if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _debounceTimer = Timer(_debounce, () {
 | 
				
			||||||
 | 
					      ref.read(postSearchNotifierProvider.notifier).search(query);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(
 | 
				
			||||||
 | 
					        title: TextField(
 | 
				
			||||||
 | 
					          controller: _searchController,
 | 
				
			||||||
 | 
					          decoration: InputDecoration(
 | 
				
			||||||
 | 
					            hintText: 'Search posts...',
 | 
				
			||||||
 | 
					            border: InputBorder.none,
 | 
				
			||||||
 | 
					            hintStyle: TextStyle(
 | 
				
			||||||
 | 
					              color: Theme.of(context).appBarTheme.foregroundColor,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          style: TextStyle(
 | 
				
			||||||
 | 
					            color: Theme.of(context).appBarTheme.foregroundColor,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          onChanged: _onSearchChanged,
 | 
				
			||||||
 | 
					          onSubmitted: (value) {
 | 
				
			||||||
 | 
					            ref.read(postSearchNotifierProvider.notifier).search(value);
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          autofocus: true,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      body: Consumer(
 | 
				
			||||||
 | 
					        builder: (context, ref, child) {
 | 
				
			||||||
 | 
					          final searchState = ref.watch(postSearchNotifierProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return searchState.when(
 | 
				
			||||||
 | 
					            data: (data) {
 | 
				
			||||||
 | 
					              if (data.items.isEmpty && _searchController.text.isNotEmpty) {
 | 
				
			||||||
 | 
					                return const Center(child: Text('No results found'));
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              return ListView.builder(
 | 
				
			||||||
 | 
					                itemCount: data.items.length + (data.hasMore ? 1 : 0),
 | 
				
			||||||
 | 
					                itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                  if (index >= data.items.length) {
 | 
				
			||||||
 | 
					                    ref
 | 
				
			||||||
 | 
					                        .read(postSearchNotifierProvider.notifier)
 | 
				
			||||||
 | 
					                        .fetch(cursor: data.nextCursor);
 | 
				
			||||||
 | 
					                    return const Center(child: CircularProgressIndicator());
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  final post = data.items[index];
 | 
				
			||||||
 | 
					                  return Column(
 | 
				
			||||||
 | 
					                    children: [PostItem(item: post), const Divider(height: 1)],
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            loading: () => const Center(child: CircularProgressIndicator()),
 | 
				
			||||||
 | 
					            error: (error, stack) => Center(child: Text('Error: $error')),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -22,7 +22,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			|||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
					import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
 | 
				
			||||||
import 'package:styled_widget/styled_widget.dart';
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part 'detail.g.dart';
 | 
					part 'realm_detail.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@riverpod
 | 
					@riverpod
 | 
				
			||||||
Future<Color?> realmAppbarForegroundColor(Ref ref, String realmSlug) async {
 | 
					Future<Color?> realmAppbarForegroundColor(Ref ref, String realmSlug) async {
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
					// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part of 'detail.dart';
 | 
					part of 'realm_detail.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// **************************************************************************
 | 
					// **************************************************************************
 | 
				
			||||||
// RiverpodGenerator
 | 
					// RiverpodGenerator
 | 
				
			||||||
@@ -344,7 +344,10 @@ class EditRealmScreen extends HookConsumerWidget {
 | 
				
			|||||||
                const SizedBox(height: 16),
 | 
					                const SizedBox(height: 16),
 | 
				
			||||||
                TextFormField(
 | 
					                TextFormField(
 | 
				
			||||||
                  controller: descriptionController,
 | 
					                  controller: descriptionController,
 | 
				
			||||||
                  decoration: InputDecoration(labelText: 'description'.tr()),
 | 
					                  decoration: InputDecoration(
 | 
				
			||||||
 | 
					                    labelText: 'description'.tr(),
 | 
				
			||||||
 | 
					                    alignLabelWithHint: true,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                  minLines: 3,
 | 
					                  minLines: 3,
 | 
				
			||||||
                  maxLines: null,
 | 
					                  maxLines: null,
 | 
				
			||||||
                  onTapOutside:
 | 
					                  onTapOutside:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								lib/widgets/article/article_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lib/widgets/article/article_list.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@@ -13,6 +13,7 @@ import 'package:island/pods/config.dart';
 | 
				
			|||||||
import 'package:island/pods/network.dart';
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
import 'package:island/widgets/alert.dart';
 | 
					import 'package:island/widgets/alert.dart';
 | 
				
			||||||
import 'package:island/widgets/content/cloud_files.dart';
 | 
					import 'package:island/widgets/content/cloud_files.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/sheet.dart';
 | 
				
			||||||
import 'package:path/path.dart' show extension;
 | 
					import 'package:path/path.dart' show extension;
 | 
				
			||||||
import 'package:path_provider/path_provider.dart';
 | 
					import 'package:path_provider/path_provider.dart';
 | 
				
			||||||
import 'package:photo_view/photo_view.dart';
 | 
					import 'package:photo_view/photo_view.dart';
 | 
				
			||||||
@@ -210,6 +211,124 @@ class CloudFileZoomIn extends HookConsumerWidget {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Widget buildInfoRow(IconData icon, String label, String value) {
 | 
				
			||||||
 | 
					      return Padding(
 | 
				
			||||||
 | 
					        padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
 | 
				
			||||||
 | 
					        child: Row(
 | 
				
			||||||
 | 
					          children: [
 | 
				
			||||||
 | 
					            Icon(
 | 
				
			||||||
 | 
					              icon,
 | 
				
			||||||
 | 
					              size: 20,
 | 
				
			||||||
 | 
					              color: Theme.of(context).colorScheme.onSurface,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            const SizedBox(width: 12),
 | 
				
			||||||
 | 
					            Text(
 | 
				
			||||||
 | 
					              label,
 | 
				
			||||||
 | 
					              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
 | 
					                color: Theme.of(context).textTheme.bodySmall?.color,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            const Spacer(),
 | 
				
			||||||
 | 
					            Flexible(
 | 
				
			||||||
 | 
					              child: Text(
 | 
				
			||||||
 | 
					                value,
 | 
				
			||||||
 | 
					                style: Theme.of(
 | 
				
			||||||
 | 
					                  context,
 | 
				
			||||||
 | 
					                ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500),
 | 
				
			||||||
 | 
					                textAlign: TextAlign.end,
 | 
				
			||||||
 | 
					                overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    String _formatFileSize(int bytes) {
 | 
				
			||||||
 | 
					      if (bytes <= 0) return '0 B';
 | 
				
			||||||
 | 
					      if (bytes < 1024) return '$bytes B';
 | 
				
			||||||
 | 
					      if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB';
 | 
				
			||||||
 | 
					      if (bytes < 1024 * 1024 * 1024) {
 | 
				
			||||||
 | 
					        return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void showInfoSheet() {
 | 
				
			||||||
 | 
					      final theme = Theme.of(context);
 | 
				
			||||||
 | 
					      final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      showModalBottomSheet(
 | 
				
			||||||
 | 
					        useRootNavigator: true,
 | 
				
			||||||
 | 
					        context: context,
 | 
				
			||||||
 | 
					        isScrollControlled: true,
 | 
				
			||||||
 | 
					        builder:
 | 
				
			||||||
 | 
					            (context) => SheetScaffold(
 | 
				
			||||||
 | 
					              titleText: 'File Information',
 | 
				
			||||||
 | 
					              child: SingleChildScrollView(
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    buildInfoRow(Icons.description, 'Name', item.name),
 | 
				
			||||||
 | 
					                    const Divider(height: 1),
 | 
				
			||||||
 | 
					                    buildInfoRow(
 | 
				
			||||||
 | 
					                      Icons.storage,
 | 
				
			||||||
 | 
					                      'Size',
 | 
				
			||||||
 | 
					                      _formatFileSize(item.size),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    const Divider(height: 1),
 | 
				
			||||||
 | 
					                    buildInfoRow(
 | 
				
			||||||
 | 
					                      Icons.category,
 | 
				
			||||||
 | 
					                      'Type',
 | 
				
			||||||
 | 
					                      item.mimeType?.toUpperCase() ?? 'UNKNOWN',
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    if (exifData.isNotEmpty) ...[
 | 
				
			||||||
 | 
					                      const SizedBox(height: 16),
 | 
				
			||||||
 | 
					                      Text(
 | 
				
			||||||
 | 
					                        'EXIF Data',
 | 
				
			||||||
 | 
					                        style: theme.textTheme.titleMedium?.copyWith(
 | 
				
			||||||
 | 
					                          fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ).padding(horizontal: 24),
 | 
				
			||||||
 | 
					                      const SizedBox(height: 8),
 | 
				
			||||||
 | 
					                      Column(
 | 
				
			||||||
 | 
					                        crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					                        children: [
 | 
				
			||||||
 | 
					                          ...exifData.entries.map(
 | 
				
			||||||
 | 
					                            (entry) => Padding(
 | 
				
			||||||
 | 
					                              padding: const EdgeInsets.symmetric(vertical: 4),
 | 
				
			||||||
 | 
					                              child: Row(
 | 
				
			||||||
 | 
					                                crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					                                children: [
 | 
				
			||||||
 | 
					                                  Text(
 | 
				
			||||||
 | 
					                                    '• ${entry.key.contains('-') ? entry.key.split('-').last : entry.key}: ',
 | 
				
			||||||
 | 
					                                    style: theme.textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
 | 
					                                      fontWeight: FontWeight.w500,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                  Expanded(
 | 
				
			||||||
 | 
					                                    child: Text(
 | 
				
			||||||
 | 
					                                      '${entry.value}'.isNotEmpty
 | 
				
			||||||
 | 
					                                          ? '${entry.value}'
 | 
				
			||||||
 | 
					                                          : 'N/A',
 | 
				
			||||||
 | 
					                                      style: theme.textTheme.bodyMedium,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                ],
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                      ).padding(horizontal: 24),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    const SizedBox(height: 16),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return DismissiblePage(
 | 
					    return DismissiblePage(
 | 
				
			||||||
      isFullScreen: true,
 | 
					      isFullScreen: true,
 | 
				
			||||||
      backgroundColor: Colors.transparent,
 | 
					      backgroundColor: Colors.transparent,
 | 
				
			||||||
@@ -288,8 +407,22 @@ class CloudFileZoomIn extends HookConsumerWidget {
 | 
				
			|||||||
            left: 16,
 | 
					            left: 16,
 | 
				
			||||||
            right: 16,
 | 
					            right: 16,
 | 
				
			||||||
            child: Row(
 | 
					            child: Row(
 | 
				
			||||||
              mainAxisAlignment: MainAxisAlignment.end,
 | 
					 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
 | 
					                IconButton(
 | 
				
			||||||
 | 
					                  icon: Icon(
 | 
				
			||||||
 | 
					                    Icons.info_outline,
 | 
				
			||||||
 | 
					                    color: Colors.white,
 | 
				
			||||||
 | 
					                    shadows: [
 | 
				
			||||||
 | 
					                      Shadow(
 | 
				
			||||||
 | 
					                        color: Colors.black54,
 | 
				
			||||||
 | 
					                        blurRadius: 5.0,
 | 
				
			||||||
 | 
					                        offset: Offset(1.0, 1.0),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  onPressed: showInfoSheet,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                Spacer(),
 | 
				
			||||||
                IconButton(
 | 
					                IconButton(
 | 
				
			||||||
                  icon: Icon(Icons.remove, color: Colors.white),
 | 
					                  icon: Icon(Icons.remove, color: Colors.white),
 | 
				
			||||||
                  onPressed: () {
 | 
					                  onPressed: () {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,9 +74,7 @@ class MarkdownTextContent extends HookConsumerWidget {
 | 
				
			|||||||
              final url = Uri.tryParse(href);
 | 
					              final url = Uri.tryParse(href);
 | 
				
			||||||
              if (url != null) {
 | 
					              if (url != null) {
 | 
				
			||||||
                if (url.scheme == 'solian') {
 | 
					                if (url.scheme == 'solian') {
 | 
				
			||||||
                  context.push(
 | 
					                  context.push(['', url.host, ...url.pathSegments].join('/'));
 | 
				
			||||||
                    ['', url.host, ...url.pathSegments].join('/'),
 | 
					 | 
				
			||||||
                  );
 | 
					 | 
				
			||||||
                  return;
 | 
					                  return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                final whitelistDomains = ['solian.app', 'solsynth.dev'];
 | 
					                final whitelistDomains = ['solian.app', 'solsynth.dev'];
 | 
				
			||||||
@@ -143,17 +141,27 @@ class MarkdownTextContent extends HookConsumerWidget {
 | 
				
			|||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      generator: MarkdownGenerator(
 | 
					      generator: MarkdownTextContent.buildGenerator(
 | 
				
			||||||
        generators: [latexGenerator],
 | 
					        isDark: isDark,
 | 
				
			||||||
        inlineSyntaxList: [
 | 
					        linesMargin: linesMargin,
 | 
				
			||||||
          _UserNameCardInlineSyntax(),
 | 
					 | 
				
			||||||
          _StickerInlineSyntax(),
 | 
					 | 
				
			||||||
          LatexSyntax(isDark),
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        linesMargin: linesMargin ?? EdgeInsets.symmetric(vertical: 4),
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static MarkdownGenerator buildGenerator({
 | 
				
			||||||
 | 
					    bool isDark = false,
 | 
				
			||||||
 | 
					    EdgeInsets? linesMargin,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    return MarkdownGenerator(
 | 
				
			||||||
 | 
					      generators: [latexGenerator],
 | 
				
			||||||
 | 
					      inlineSyntaxList: [
 | 
				
			||||||
 | 
					        _UserNameCardInlineSyntax(),
 | 
				
			||||||
 | 
					        _StickerInlineSyntax(),
 | 
				
			||||||
 | 
					        LatexSyntax(isDark),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      linesMargin: linesMargin ?? EdgeInsets.symmetric(vertical: 4),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _UserNameCardInlineSyntax extends markdown.InlineSyntax {
 | 
					class _UserNameCardInlineSyntax extends markdown.InlineSyntax {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										51
									
								
								lib/widgets/empty_state.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								lib/widgets/empty_state.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EmptyState extends StatelessWidget {
 | 
				
			||||||
 | 
					  final IconData icon;
 | 
				
			||||||
 | 
					  final String title;
 | 
				
			||||||
 | 
					  final String description;
 | 
				
			||||||
 | 
					  final Widget? action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const EmptyState({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.icon,
 | 
				
			||||||
 | 
					    required this.title,
 | 
				
			||||||
 | 
					    required this.description,
 | 
				
			||||||
 | 
					    this.action,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Center(
 | 
				
			||||||
 | 
					      child: Padding(
 | 
				
			||||||
 | 
					        padding: const EdgeInsets.all(24.0),
 | 
				
			||||||
 | 
					        child: Column(
 | 
				
			||||||
 | 
					          mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
 | 
					          children: [
 | 
				
			||||||
 | 
					            Icon(
 | 
				
			||||||
 | 
					              icon,
 | 
				
			||||||
 | 
					              size: 64,
 | 
				
			||||||
 | 
					              color: Theme.of(context).colorScheme.outline,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            const SizedBox(height: 16),
 | 
				
			||||||
 | 
					            Text(
 | 
				
			||||||
 | 
					              title,
 | 
				
			||||||
 | 
					              style: Theme.of(context).textTheme.titleLarge,
 | 
				
			||||||
 | 
					              textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            const SizedBox(height: 8),
 | 
				
			||||||
 | 
					            Text(
 | 
				
			||||||
 | 
					              description,
 | 
				
			||||||
 | 
					              style: Theme.of(context).textTheme.bodyMedium,
 | 
				
			||||||
 | 
					              textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            if (action != null) ...[
 | 
				
			||||||
 | 
					              const SizedBox(height: 24),
 | 
				
			||||||
 | 
					              action!,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								lib/widgets/loading_indicator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/widgets/loading_indicator.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A simple loading indicator widget that can be used throughout the app
 | 
				
			||||||
 | 
					class LoadingIndicator extends StatelessWidget {
 | 
				
			||||||
 | 
					  /// The size of the loading indicator
 | 
				
			||||||
 | 
					  final double size;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The color of the loading indicator
 | 
				
			||||||
 | 
					  final Color? color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Creates a loading indicator
 | 
				
			||||||
 | 
					  const LoadingIndicator({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    this.size = 24.0,
 | 
				
			||||||
 | 
					    this.color,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return SizedBox(
 | 
				
			||||||
 | 
					      width: size,
 | 
				
			||||||
 | 
					      height: size,
 | 
				
			||||||
 | 
					      child: CircularProgressIndicator(
 | 
				
			||||||
 | 
					        strokeWidth: 2.0,
 | 
				
			||||||
 | 
					        valueColor: color != null
 | 
				
			||||||
 | 
					            ? AlwaysStoppedAnimation<Color>(
 | 
				
			||||||
 | 
					                color!,
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            : null,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -542,14 +542,14 @@ Widget _buildReferencePost(BuildContext context, SnPost item) {
 | 
				
			|||||||
class PostReactionList extends HookConsumerWidget {
 | 
					class PostReactionList extends HookConsumerWidget {
 | 
				
			||||||
  final String parentId;
 | 
					  final String parentId;
 | 
				
			||||||
  final Map<String, int> reactions;
 | 
					  final Map<String, int> reactions;
 | 
				
			||||||
  final Function(String symbol, int attitude, int delta) onReact;
 | 
					  final Function(String symbol, int attitude, int delta)? onReact;
 | 
				
			||||||
  final EdgeInsets? padding;
 | 
					  final EdgeInsets? padding;
 | 
				
			||||||
  const PostReactionList({
 | 
					  const PostReactionList({
 | 
				
			||||||
    super.key,
 | 
					    super.key,
 | 
				
			||||||
    required this.parentId,
 | 
					    required this.parentId,
 | 
				
			||||||
    required this.reactions,
 | 
					    required this.reactions,
 | 
				
			||||||
    this.padding,
 | 
					    this.padding,
 | 
				
			||||||
    required this.onReact,
 | 
					    this.onReact,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@@ -570,7 +570,7 @@ class PostReactionList extends HookConsumerWidget {
 | 
				
			|||||||
          })
 | 
					          })
 | 
				
			||||||
          .then((resp) {
 | 
					          .then((resp) {
 | 
				
			||||||
            var isRemoving = resp.statusCode == 204;
 | 
					            var isRemoving = resp.statusCode == 204;
 | 
				
			||||||
            onReact(symbol, attitude, isRemoving ? -1 : 1);
 | 
					            onReact?.call(symbol, attitude, isRemoving ? -1 : 1);
 | 
				
			||||||
            HapticFeedback.heavyImpact();
 | 
					            HapticFeedback.heavyImpact();
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
      submitting.value = false;
 | 
					      submitting.value = false;
 | 
				
			||||||
@@ -582,33 +582,34 @@ class PostReactionList extends HookConsumerWidget {
 | 
				
			|||||||
        scrollDirection: Axis.horizontal,
 | 
					        scrollDirection: Axis.horizontal,
 | 
				
			||||||
        padding: padding ?? EdgeInsets.zero,
 | 
					        padding: padding ?? EdgeInsets.zero,
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          Padding(
 | 
					          if (onReact != null)
 | 
				
			||||||
            padding: const EdgeInsets.only(right: 8),
 | 
					            Padding(
 | 
				
			||||||
            child: ActionChip(
 | 
					              padding: const EdgeInsets.only(right: 8),
 | 
				
			||||||
              avatar: Icon(Symbols.add_reaction),
 | 
					              child: ActionChip(
 | 
				
			||||||
              label: Text('react').tr(),
 | 
					                avatar: Icon(Symbols.add_reaction),
 | 
				
			||||||
              visualDensity: const VisualDensity(
 | 
					                label: Text('react').tr(),
 | 
				
			||||||
                horizontal: VisualDensity.minimumDensity,
 | 
					                visualDensity: const VisualDensity(
 | 
				
			||||||
                vertical: VisualDensity.minimumDensity,
 | 
					                  horizontal: VisualDensity.minimumDensity,
 | 
				
			||||||
 | 
					                  vertical: VisualDensity.minimumDensity,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                onPressed:
 | 
				
			||||||
 | 
					                    submitting.value
 | 
				
			||||||
 | 
					                        ? null
 | 
				
			||||||
 | 
					                        : () {
 | 
				
			||||||
 | 
					                          showModalBottomSheet(
 | 
				
			||||||
 | 
					                            context: context,
 | 
				
			||||||
 | 
					                            builder: (BuildContext context) {
 | 
				
			||||||
 | 
					                              return _PostReactionSheet(
 | 
				
			||||||
 | 
					                                reactionsCount: reactions,
 | 
				
			||||||
 | 
					                                onReact: (symbol, attitude) {
 | 
				
			||||||
 | 
					                                  reactPost(symbol, attitude);
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                              );
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                          );
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              onPressed:
 | 
					 | 
				
			||||||
                  submitting.value
 | 
					 | 
				
			||||||
                      ? null
 | 
					 | 
				
			||||||
                      : () {
 | 
					 | 
				
			||||||
                        showModalBottomSheet(
 | 
					 | 
				
			||||||
                          context: context,
 | 
					 | 
				
			||||||
                          builder: (BuildContext context) {
 | 
					 | 
				
			||||||
                            return _PostReactionSheet(
 | 
					 | 
				
			||||||
                              reactionsCount: reactions,
 | 
					 | 
				
			||||||
                              onReact: (symbol, attitude) {
 | 
					 | 
				
			||||||
                                reactPost(symbol, attitude);
 | 
					 | 
				
			||||||
                              },
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
                          },
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                      },
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          for (final symbol in reactions.keys)
 | 
					          for (final symbol in reactions.keys)
 | 
				
			||||||
            Padding(
 | 
					            Padding(
 | 
				
			||||||
              padding: const EdgeInsets.only(right: 8),
 | 
					              padding: const EdgeInsets.only(right: 8),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -365,11 +365,6 @@ class PostItemCreator extends HookConsumerWidget {
 | 
				
			|||||||
          parentId: item.id,
 | 
					          parentId: item.id,
 | 
				
			||||||
          reactions: item.reactionsCount,
 | 
					          reactions: item.reactionsCount,
 | 
				
			||||||
          padding: EdgeInsets.zero,
 | 
					          padding: EdgeInsets.zero,
 | 
				
			||||||
          onReact: (symbol, attitude, delta) {
 | 
					 | 
				
			||||||
            final reactionsCount = Map<String, int>.from(item.reactionsCount);
 | 
					 | 
				
			||||||
            reactionsCount[symbol] = (reactionsCount[symbol] ?? 0) + delta;
 | 
					 | 
				
			||||||
            onUpdate?.call(item.copyWith(reactionsCount: reactionsCount));
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        const Gap(16),
 | 
					        const Gap(16),
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										139
									
								
								lib/widgets/web_article_card.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								lib/widgets/web_article_card.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					import 'package:cached_network_image/cached_network_image.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/webfeed.dart';
 | 
				
			||||||
 | 
					import 'package:island/services/time.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebArticleCard extends StatelessWidget {
 | 
				
			||||||
 | 
					  final SnWebArticle article;
 | 
				
			||||||
 | 
					  final double? maxWidth;
 | 
				
			||||||
 | 
					  final bool showDetails;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const WebArticleCard({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.article,
 | 
				
			||||||
 | 
					    this.maxWidth,
 | 
				
			||||||
 | 
					    this.showDetails = false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onTap(BuildContext context) {
 | 
				
			||||||
 | 
					    context.push('/feeds/articles/${article.id}');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final theme = Theme.of(context);
 | 
				
			||||||
 | 
					    final colorScheme = theme.colorScheme;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ConstrainedBox(
 | 
				
			||||||
 | 
					      constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
 | 
				
			||||||
 | 
					      child: Card(
 | 
				
			||||||
 | 
					        clipBehavior: Clip.antiAlias,
 | 
				
			||||||
 | 
					        child: InkWell(
 | 
				
			||||||
 | 
					          onTap: () => _onTap(context),
 | 
				
			||||||
 | 
					          child: AspectRatio(
 | 
				
			||||||
 | 
					            aspectRatio: 16 / 9,
 | 
				
			||||||
 | 
					            child: Stack(
 | 
				
			||||||
 | 
					              fit: StackFit.expand,
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                // Image or fallback
 | 
				
			||||||
 | 
					                article.preview?.imageUrl != null
 | 
				
			||||||
 | 
					                    ? CachedNetworkImage(
 | 
				
			||||||
 | 
					                      imageUrl: article.preview!.imageUrl!,
 | 
				
			||||||
 | 
					                      fit: BoxFit.cover,
 | 
				
			||||||
 | 
					                      width: double.infinity,
 | 
				
			||||||
 | 
					                      height: double.infinity,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    : ColoredBox(
 | 
				
			||||||
 | 
					                      color: colorScheme.secondaryContainer,
 | 
				
			||||||
 | 
					                      child: const Center(
 | 
				
			||||||
 | 
					                        child: Icon(
 | 
				
			||||||
 | 
					                          Icons.article_outlined,
 | 
				
			||||||
 | 
					                          size: 48,
 | 
				
			||||||
 | 
					                          color: Colors.white,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                // Gradient overlay
 | 
				
			||||||
 | 
					                Container(
 | 
				
			||||||
 | 
					                  decoration: BoxDecoration(
 | 
				
			||||||
 | 
					                    gradient: LinearGradient(
 | 
				
			||||||
 | 
					                      begin: Alignment.topCenter,
 | 
				
			||||||
 | 
					                      end: Alignment.bottomCenter,
 | 
				
			||||||
 | 
					                      colors: [
 | 
				
			||||||
 | 
					                        Colors.transparent,
 | 
				
			||||||
 | 
					                        Colors.black.withOpacity(0.7),
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                // Title
 | 
				
			||||||
 | 
					                Align(
 | 
				
			||||||
 | 
					                  alignment: Alignment.bottomLeft,
 | 
				
			||||||
 | 
					                  child: Container(
 | 
				
			||||||
 | 
					                    padding: const EdgeInsets.only(
 | 
				
			||||||
 | 
					                      left: 12,
 | 
				
			||||||
 | 
					                      right: 12,
 | 
				
			||||||
 | 
					                      bottom: 8,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    child: Column(
 | 
				
			||||||
 | 
					                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					                      mainAxisAlignment: MainAxisAlignment.end,
 | 
				
			||||||
 | 
					                      mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					                      children: [
 | 
				
			||||||
 | 
					                        if (showDetails)
 | 
				
			||||||
 | 
					                          const SizedBox(height: 8)
 | 
				
			||||||
 | 
					                        else
 | 
				
			||||||
 | 
					                          Spacer(),
 | 
				
			||||||
 | 
					                        Text(
 | 
				
			||||||
 | 
					                          article.title,
 | 
				
			||||||
 | 
					                          style: theme.textTheme.titleSmall?.copyWith(
 | 
				
			||||||
 | 
					                            color: Colors.white,
 | 
				
			||||||
 | 
					                            fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                            height: 1.3,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          maxLines: showDetails ? 3 : 2,
 | 
				
			||||||
 | 
					                          overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        if (showDetails &&
 | 
				
			||||||
 | 
					                            article.author?.isNotEmpty == true) ...[
 | 
				
			||||||
 | 
					                          const SizedBox(height: 4),
 | 
				
			||||||
 | 
					                          Text(
 | 
				
			||||||
 | 
					                            article.author!,
 | 
				
			||||||
 | 
					                            style: TextStyle(
 | 
				
			||||||
 | 
					                              fontSize: 10,
 | 
				
			||||||
 | 
					                              color: Colors.white.withOpacity(0.9),
 | 
				
			||||||
 | 
					                              fontWeight: FontWeight.w500,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        if (showDetails) const Spacer(),
 | 
				
			||||||
 | 
					                        if (showDetails && article.publishedAt != null) ...[
 | 
				
			||||||
 | 
					                          Text(
 | 
				
			||||||
 | 
					                            '${article.publishedAt!.formatSystem()} · ${article.publishedAt!.formatRelative(context)}',
 | 
				
			||||||
 | 
					                            style: const TextStyle(
 | 
				
			||||||
 | 
					                              fontSize: 9,
 | 
				
			||||||
 | 
					                              color: Colors.white70,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          const SizedBox(height: 2),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        Text(
 | 
				
			||||||
 | 
					                          article.feed?.title ?? 'Unknown Source',
 | 
				
			||||||
 | 
					                          style: const TextStyle(
 | 
				
			||||||
 | 
					                            fontSize: 9,
 | 
				
			||||||
 | 
					                            color: Colors.white70,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -11,32 +11,32 @@ PODS:
 | 
				
			|||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
  - file_selector_macos (0.0.1):
 | 
					  - file_selector_macos (0.0.1):
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
  - Firebase/CoreOnly (11.13.0):
 | 
					  - Firebase/CoreOnly (11.15.0):
 | 
				
			||||||
    - FirebaseCore (~> 11.13.0)
 | 
					    - FirebaseCore (~> 11.15.0)
 | 
				
			||||||
  - Firebase/Messaging (11.13.0):
 | 
					  - Firebase/Messaging (11.15.0):
 | 
				
			||||||
    - Firebase/CoreOnly
 | 
					    - Firebase/CoreOnly
 | 
				
			||||||
    - FirebaseMessaging (~> 11.13.0)
 | 
					    - FirebaseMessaging (~> 11.15.0)
 | 
				
			||||||
  - firebase_core (3.14.0):
 | 
					  - firebase_core (3.15.0):
 | 
				
			||||||
    - Firebase/CoreOnly (~> 11.13.0)
 | 
					    - Firebase/CoreOnly (~> 11.15.0)
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
  - firebase_messaging (15.2.7):
 | 
					  - firebase_messaging (15.2.8):
 | 
				
			||||||
    - Firebase/CoreOnly (~> 11.13.0)
 | 
					    - Firebase/CoreOnly (~> 11.15.0)
 | 
				
			||||||
    - Firebase/Messaging (~> 11.13.0)
 | 
					    - Firebase/Messaging (~> 11.15.0)
 | 
				
			||||||
    - firebase_core
 | 
					    - firebase_core
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
  - FirebaseCore (11.13.0):
 | 
					  - FirebaseCore (11.15.0):
 | 
				
			||||||
    - FirebaseCoreInternal (~> 11.13.0)
 | 
					    - FirebaseCoreInternal (~> 11.15.0)
 | 
				
			||||||
    - GoogleUtilities/Environment (~> 8.1)
 | 
					    - GoogleUtilities/Environment (~> 8.1)
 | 
				
			||||||
    - GoogleUtilities/Logger (~> 8.1)
 | 
					    - GoogleUtilities/Logger (~> 8.1)
 | 
				
			||||||
  - FirebaseCoreInternal (11.13.0):
 | 
					  - FirebaseCoreInternal (11.15.0):
 | 
				
			||||||
    - "GoogleUtilities/NSData+zlib (~> 8.1)"
 | 
					    - "GoogleUtilities/NSData+zlib (~> 8.1)"
 | 
				
			||||||
  - FirebaseInstallations (11.13.0):
 | 
					  - FirebaseInstallations (11.15.0):
 | 
				
			||||||
    - FirebaseCore (~> 11.13.0)
 | 
					    - FirebaseCore (~> 11.15.0)
 | 
				
			||||||
    - GoogleUtilities/Environment (~> 8.1)
 | 
					    - GoogleUtilities/Environment (~> 8.1)
 | 
				
			||||||
    - GoogleUtilities/UserDefaults (~> 8.1)
 | 
					    - GoogleUtilities/UserDefaults (~> 8.1)
 | 
				
			||||||
    - PromisesObjC (~> 2.4)
 | 
					    - PromisesObjC (~> 2.4)
 | 
				
			||||||
  - FirebaseMessaging (11.13.0):
 | 
					  - FirebaseMessaging (11.15.0):
 | 
				
			||||||
    - FirebaseCore (~> 11.13.0)
 | 
					    - FirebaseCore (~> 11.15.0)
 | 
				
			||||||
    - FirebaseInstallations (~> 11.0)
 | 
					    - FirebaseInstallations (~> 11.0)
 | 
				
			||||||
    - GoogleDataTransport (~> 10.0)
 | 
					    - GoogleDataTransport (~> 10.0)
 | 
				
			||||||
    - GoogleUtilities/AppDelegateSwizzler (~> 8.1)
 | 
					    - GoogleUtilities/AppDelegateSwizzler (~> 8.1)
 | 
				
			||||||
@@ -92,7 +92,7 @@ PODS:
 | 
				
			|||||||
    - GoogleUtilities/Privacy
 | 
					    - GoogleUtilities/Privacy
 | 
				
			||||||
  - irondash_engine_context (0.0.1):
 | 
					  - irondash_engine_context (0.0.1):
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
  - livekit_client (2.4.8):
 | 
					  - livekit_client (2.4.9):
 | 
				
			||||||
    - flutter_webrtc
 | 
					    - flutter_webrtc
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
    - WebRTC-SDK (= 125.6422.07)
 | 
					    - WebRTC-SDK (= 125.6422.07)
 | 
				
			||||||
@@ -291,13 +291,13 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
 | 
					  device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
 | 
				
			||||||
  file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
 | 
					  file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
 | 
				
			||||||
  file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
 | 
					  file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
 | 
				
			||||||
  Firebase: 3435bc66b4d494c2f22c79fd3aae4c1db6662327
 | 
					  Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
 | 
				
			||||||
  firebase_core: 1095fcf33161d99bc34aa10f7c0d89414a208d15
 | 
					  firebase_core: 177f51be1650b15d2d5b9f1abf48792619288070
 | 
				
			||||||
  firebase_messaging: 6417056ffb85141607618ddfef9fec9f3caab3ea
 | 
					  firebase_messaging: 8748a5d4bb435993cffa7f5501292f3e914a23d7
 | 
				
			||||||
  FirebaseCore: c692c7f1c75305ab6aff2b367f25e11d73aa8bd0
 | 
					  FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
 | 
				
			||||||
  FirebaseCoreInternal: 29d7b3af4aaf0b8f3ed20b568c13df399b06f68c
 | 
					  FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
 | 
				
			||||||
  FirebaseInstallations: 0ee9074f2c1e86561ace168ee1470dc67aabaf02
 | 
					  FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
 | 
				
			||||||
  FirebaseMessaging: 195bbdb73e6ca1dbc76cd46e73f3552c084ef6e4
 | 
					  FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
 | 
				
			||||||
  flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
 | 
					  flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
 | 
				
			||||||
  flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
 | 
					  flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
 | 
				
			||||||
  flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
 | 
					  flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
 | 
				
			||||||
@@ -309,7 +309,7 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
 | 
					  GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
 | 
				
			||||||
  GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
 | 
					  GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
 | 
				
			||||||
  irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
 | 
					  irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
 | 
				
			||||||
  livekit_client: 6a35243df3da61750c98e266e02dedcf5d25c888
 | 
					  livekit_client: c9d9f41996f5cf22b9ba0e8483e6af4ca5094059
 | 
				
			||||||
  local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
 | 
					  local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
 | 
				
			||||||
  media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
 | 
					  media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
 | 
				
			||||||
  media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
 | 
					  media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										140
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								pubspec.lock
									
									
									
									
									
								
							@@ -13,10 +13,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: _flutterfire_internals
 | 
					      name: _flutterfire_internals
 | 
				
			||||||
      sha256: dda4fd7909a732a014239009aa52537b136f8ce568de23c212587097887e2307
 | 
					      sha256: "50e24b769bd1e725732f0aff18b806b8731c1fbcf4e8018ab98e7c4805a2a52f"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.3.56"
 | 
					    version: "1.3.57"
 | 
				
			||||||
  analyzer:
 | 
					  analyzer:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -629,50 +629,50 @@ packages:
 | 
				
			|||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: firebase_core
 | 
					      name: firebase_core
 | 
				
			||||||
      sha256: "420d9111dcf095341f1ea8fdce926eef750cf7b9745d21f38000de780c94f608"
 | 
					      sha256: "5bba5924139e91d26446fd2601c18a6aa62c1161c768a989bb5e245dcdc20644"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "3.14.0"
 | 
					    version: "3.15.0"
 | 
				
			||||||
  firebase_core_platform_interface:
 | 
					  firebase_core_platform_interface:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: firebase_core_platform_interface
 | 
					      name: firebase_core_platform_interface
 | 
				
			||||||
      sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
 | 
					      sha256: "5d2ab45779d91af2aa0252dec9fe4ee1caa015d83377de255454dcaa1526a0e0"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "5.4.0"
 | 
					    version: "5.4.1"
 | 
				
			||||||
  firebase_core_web:
 | 
					  firebase_core_web:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: firebase_core_web
 | 
					      name: firebase_core_web
 | 
				
			||||||
      sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e
 | 
					      sha256: eb3afccfc452b2b2075acbe0c4b27de62dd596802b4e5e19869c1e926cbb20b3
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.23.0"
 | 
					    version: "2.24.0"
 | 
				
			||||||
  firebase_messaging:
 | 
					  firebase_messaging:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: firebase_messaging
 | 
					      name: firebase_messaging
 | 
				
			||||||
      sha256: "758461f67b96aa5ad27625aaae39882fd6d1961b1c7e005301f9a74b6336100b"
 | 
					      sha256: c6711cf2f455532b84a94022c7aaf85088849763af2f01b775ca79d82d10a01a
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "15.2.7"
 | 
					    version: "15.2.8"
 | 
				
			||||||
  firebase_messaging_platform_interface:
 | 
					  firebase_messaging_platform_interface:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: firebase_messaging_platform_interface
 | 
					      name: firebase_messaging_platform_interface
 | 
				
			||||||
      sha256: "614db1b0df0f53e541e41cc182b6d7ede5763c400f6ba232a5f8d0e1b5e5de32"
 | 
					      sha256: "1c9dacccb1aee1bf17ba519dda5563a16fdd2ec1e79b5f2e421cb4bf75a166f7"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "4.6.7"
 | 
					    version: "4.6.8"
 | 
				
			||||||
  firebase_messaging_web:
 | 
					  firebase_messaging_web:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: firebase_messaging_web
 | 
					      name: firebase_messaging_web
 | 
				
			||||||
      sha256: b5fbbcdd3e0e7f3fde72b0c119410f22737638fed5fc428b54bba06bc1455d81
 | 
					      sha256: "54317c26fa92f0d90a2017977ac791cb0504eca29fcf397f06adf727d4a7a2d5"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "3.10.7"
 | 
					    version: "3.10.8"
 | 
				
			||||||
  fixnum:
 | 
					  fixnum:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -798,6 +798,54 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.6.0"
 | 
					    version: "0.6.0"
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_keyboard_visibility
 | 
				
			||||||
 | 
					      sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "6.0.0"
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility_linux:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_keyboard_visibility_linux
 | 
				
			||||||
 | 
					      sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "1.0.0"
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility_macos:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_keyboard_visibility_macos
 | 
				
			||||||
 | 
					      sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "1.0.0"
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility_platform_interface:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_keyboard_visibility_platform_interface
 | 
				
			||||||
 | 
					      sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.0.0"
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility_web:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_keyboard_visibility_web
 | 
				
			||||||
 | 
					      sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.0.0"
 | 
				
			||||||
 | 
					  flutter_keyboard_visibility_windows:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_keyboard_visibility_windows
 | 
				
			||||||
 | 
					      sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "1.0.0"
 | 
				
			||||||
  flutter_launcher_icons:
 | 
					  flutter_launcher_icons:
 | 
				
			||||||
    dependency: "direct dev"
 | 
					    dependency: "direct dev"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -960,6 +1008,14 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "4.1.1"
 | 
					    version: "4.1.1"
 | 
				
			||||||
 | 
					  flutter_typeahead:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_typeahead
 | 
				
			||||||
 | 
					      sha256: d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "5.2.0"
 | 
				
			||||||
  flutter_udid:
 | 
					  flutter_udid:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -977,10 +1033,10 @@ packages:
 | 
				
			|||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: flutter_webrtc
 | 
					      name: flutter_webrtc
 | 
				
			||||||
      sha256: dd47ca103b5b6217771e6277882674276d9621bbf9eb23da3c03898b507844e3
 | 
					      sha256: "792aa1e5838a719f29ae52c0773dbb5dd781fc33b1bf87c321b274e55ab51ad1"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.14.1"
 | 
					    version: "0.14.2"
 | 
				
			||||||
  font_awesome_flutter:
 | 
					  font_awesome_flutter:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1093,6 +1149,14 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.15.6"
 | 
					    version: "0.15.6"
 | 
				
			||||||
 | 
					  html2md:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: html2md
 | 
				
			||||||
 | 
					      sha256: "465cf8ffa1b510fe0e97941579bf5b22e2d575f2cecb500a9c0254efe33a8036"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "1.3.2"
 | 
				
			||||||
  http:
 | 
					  http:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1297,10 +1361,10 @@ packages:
 | 
				
			|||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: livekit_client
 | 
					      name: livekit_client
 | 
				
			||||||
      sha256: c270720a49b935591960c6f3296fd8f00c09b45a70cd64aef78cd0a8f8257913
 | 
					      sha256: "5d182f40cc9aafce60a9acf936bad8bc69010b5cbf0a949f6f27dc4390f2fcce"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.4.8"
 | 
					    version: "2.4.9"
 | 
				
			||||||
  local_auth:
 | 
					  local_auth:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1677,6 +1741,38 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.1.8"
 | 
					    version: "2.1.8"
 | 
				
			||||||
 | 
					  pointer_interceptor:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: pointer_interceptor
 | 
				
			||||||
 | 
					      sha256: "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "0.10.1+2"
 | 
				
			||||||
 | 
					  pointer_interceptor_ios:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: pointer_interceptor_ios
 | 
				
			||||||
 | 
					      sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "0.10.1"
 | 
				
			||||||
 | 
					  pointer_interceptor_platform_interface:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: pointer_interceptor_platform_interface
 | 
				
			||||||
 | 
					      sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "0.10.0+1"
 | 
				
			||||||
 | 
					  pointer_interceptor_web:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: pointer_interceptor_web
 | 
				
			||||||
 | 
					      sha256: "460b600e71de6fcea2b3d5f662c92293c049c4319e27f0829310e5a953b3ee2a"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "0.10.3"
 | 
				
			||||||
  pool:
 | 
					  pool:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1689,10 +1785,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: posix
 | 
					      name: posix
 | 
				
			||||||
      sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62
 | 
					      sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "6.0.2"
 | 
					    version: "6.0.3"
 | 
				
			||||||
  protobuf:
 | 
					  protobuf:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1809,10 +1905,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: record_web
 | 
					      name: record_web
 | 
				
			||||||
      sha256: "024c81eb7f51468b1833a3eca8b461c7ca25c04899dba37abe580bb57afd32e4"
 | 
					      sha256: a12856d0b3dd03d336b4b10d7520a8b3e21649a06a8f95815318feaa8f07adbb
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.1.8"
 | 
					    version: "1.1.9"
 | 
				
			||||||
  record_windows:
 | 
					  record_windows:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
 | 
				
			|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
					# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
				
			||||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
					# In Windows, build-name is used as the major, minor, and patch parts
 | 
				
			||||||
# of the product and file versions while build-number is used as the build suffix.
 | 
					# of the product and file versions while build-number is used as the build suffix.
 | 
				
			||||||
version: 3.0.0+109
 | 
					version: 3.0.0+110
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.7.2
 | 
					  sdk: ^3.7.2
 | 
				
			||||||
@@ -37,7 +37,7 @@ dependencies:
 | 
				
			|||||||
  flutter_hooks: ^0.21.2
 | 
					  flutter_hooks: ^0.21.2
 | 
				
			||||||
  hooks_riverpod: ^2.6.1
 | 
					  hooks_riverpod: ^2.6.1
 | 
				
			||||||
  bitsdojo_window: ^0.1.6
 | 
					  bitsdojo_window: ^0.1.6
 | 
				
			||||||
  go_router: ^15.2.4
 | 
					  go_router: ^15.1.3
 | 
				
			||||||
  styled_widget: ^0.4.1
 | 
					  styled_widget: ^0.4.1
 | 
				
			||||||
  shared_preferences: ^2.5.3
 | 
					  shared_preferences: ^2.5.3
 | 
				
			||||||
  flutter_riverpod: ^2.6.1
 | 
					  flutter_riverpod: ^2.6.1
 | 
				
			||||||
@@ -127,6 +127,8 @@ dependencies:
 | 
				
			|||||||
      url: https://github.com/lionelmennig/textfield_tags.git
 | 
					      url: https://github.com/lionelmennig/textfield_tags.git
 | 
				
			||||||
      ref: fixes/allow-controller-re-registration
 | 
					      ref: fixes/allow-controller-re-registration
 | 
				
			||||||
  mime: ^2.0.0
 | 
					  mime: ^2.0.0
 | 
				
			||||||
 | 
					  html2md: ^1.3.2
 | 
				
			||||||
 | 
					  flutter_typeahead: ^5.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev_dependencies:
 | 
					dev_dependencies:
 | 
				
			||||||
  flutter_test:
 | 
					  flutter_test:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user