Compare commits
	
		
			22 Commits
		
	
	
		
			9bdf8ba346
			...
			3.1.0+121
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 808e7dcffa | |||
| 9bed4fa6fb | |||
| e6255a340b | |||
| 78bf319fb7 | |||
| 36a966d582 | |||
| f72b268d36 | |||
| 44ef31034e | |||
| 229dc2186f | |||
| a2f9a1efb4 | |||
| 
						 | 
					823e3c5de6 | ||
| 
						 | 
					faac7bac35 | ||
| 1fac1bfe02 | |||
| 9394b1d9c8 | |||
| 43dd13bac4 | |||
| 65bc372103 | |||
| 6558854a7a | |||
| 892035ab27 | |||
| 87ae8d2ff4 | |||
| 15c2dbaa0d | |||
| 6b3338b885 | |||
| bb00b1bc6a | |||
| 5e1a15ada2 | 
@@ -706,6 +706,7 @@
 | 
				
			|||||||
  "copyToClipboardTooltip": "Copy to clipboard",
 | 
					  "copyToClipboardTooltip": "Copy to clipboard",
 | 
				
			||||||
  "postForwardingTo": "Forwarding to",
 | 
					  "postForwardingTo": "Forwarding to",
 | 
				
			||||||
  "postReplyingTo": "Replying to",
 | 
					  "postReplyingTo": "Replying to",
 | 
				
			||||||
 | 
					  "postReplyPlaceholder": "Post your reply",
 | 
				
			||||||
  "postEditing": "You are editing an existing post",
 | 
					  "postEditing": "You are editing an existing post",
 | 
				
			||||||
  "postArticle": "Article",
 | 
					  "postArticle": "Article",
 | 
				
			||||||
  "aboutDeviceName": "Device Name",
 | 
					  "aboutDeviceName": "Device Name",
 | 
				
			||||||
@@ -786,5 +787,7 @@
 | 
				
			|||||||
  "links": "Links",
 | 
					  "links": "Links",
 | 
				
			||||||
  "addLink": "Add link",
 | 
					  "addLink": "Add link",
 | 
				
			||||||
  "linkKey": "Link Name",
 | 
					  "linkKey": "Link Name",
 | 
				
			||||||
  "linkValue": "URL"
 | 
					  "linkValue": "URL",
 | 
				
			||||||
 | 
					  "debugOptions": "Debug Options",
 | 
				
			||||||
 | 
					  "joinedAt": "Joined at {}"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -73,6 +73,8 @@ PODS:
 | 
				
			|||||||
    - GoogleUtilities/UserDefaults (~> 8.1)
 | 
					    - GoogleUtilities/UserDefaults (~> 8.1)
 | 
				
			||||||
    - nanopb (~> 3.30910.0)
 | 
					    - nanopb (~> 3.30910.0)
 | 
				
			||||||
  - Flutter (1.0.0)
 | 
					  - Flutter (1.0.0)
 | 
				
			||||||
 | 
					  - flutter_app_update (0.0.1):
 | 
				
			||||||
 | 
					    - Flutter
 | 
				
			||||||
  - flutter_inappwebview_ios (0.0.1):
 | 
					  - flutter_inappwebview_ios (0.0.1):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - flutter_inappwebview_ios/Core (= 0.0.1)
 | 
					    - flutter_inappwebview_ios/Core (= 0.0.1)
 | 
				
			||||||
@@ -178,25 +180,25 @@ PODS:
 | 
				
			|||||||
  - sqflite_darwin (0.0.4):
 | 
					  - sqflite_darwin (0.0.4):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
  - sqlite3 (3.50.3):
 | 
					  - sqlite3 (3.50.4):
 | 
				
			||||||
    - sqlite3/common (= 3.50.3)
 | 
					    - sqlite3/common (= 3.50.4)
 | 
				
			||||||
  - sqlite3/common (3.50.3)
 | 
					  - sqlite3/common (3.50.4)
 | 
				
			||||||
  - sqlite3/dbstatvtab (3.50.3):
 | 
					  - sqlite3/dbstatvtab (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/fts5 (3.50.3):
 | 
					  - sqlite3/fts5 (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/math (3.50.3):
 | 
					  - sqlite3/math (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/perf-threadsafe (3.50.3):
 | 
					  - sqlite3/perf-threadsafe (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/rtree (3.50.3):
 | 
					  - sqlite3/rtree (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/session (3.50.3):
 | 
					  - sqlite3/session (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3_flutter_libs (0.0.1):
 | 
					  - sqlite3_flutter_libs (0.0.1):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
    - sqlite3 (~> 3.50.3)
 | 
					    - sqlite3 (~> 3.50.4)
 | 
				
			||||||
    - sqlite3/dbstatvtab
 | 
					    - sqlite3/dbstatvtab
 | 
				
			||||||
    - sqlite3/fts5
 | 
					    - sqlite3/fts5
 | 
				
			||||||
    - sqlite3/math
 | 
					    - sqlite3/math
 | 
				
			||||||
@@ -223,6 +225,7 @@ DEPENDENCIES:
 | 
				
			|||||||
  - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
 | 
					  - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
 | 
				
			||||||
  - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
 | 
					  - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
 | 
				
			||||||
  - Flutter (from `Flutter`)
 | 
					  - Flutter (from `Flutter`)
 | 
				
			||||||
 | 
					  - flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
 | 
				
			||||||
  - 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_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`)
 | 
				
			||||||
@@ -293,6 +296,8 @@ EXTERNAL SOURCES:
 | 
				
			|||||||
    :path: ".symlinks/plugins/firebase_messaging/ios"
 | 
					    :path: ".symlinks/plugins/firebase_messaging/ios"
 | 
				
			||||||
  Flutter:
 | 
					  Flutter:
 | 
				
			||||||
    :path: Flutter
 | 
					    :path: Flutter
 | 
				
			||||||
 | 
					  flutter_app_update:
 | 
				
			||||||
 | 
					    :path: ".symlinks/plugins/flutter_app_update/ios"
 | 
				
			||||||
  flutter_inappwebview_ios:
 | 
					  flutter_inappwebview_ios:
 | 
				
			||||||
    :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
 | 
					    :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
 | 
				
			||||||
  flutter_keyboard_visibility:
 | 
					  flutter_keyboard_visibility:
 | 
				
			||||||
@@ -372,6 +377,7 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
 | 
					  FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
 | 
				
			||||||
  FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
 | 
					  FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
 | 
				
			||||||
  Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
 | 
					  Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
 | 
				
			||||||
 | 
					  flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
 | 
				
			||||||
  flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
 | 
					  flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
 | 
				
			||||||
  flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
 | 
					  flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
 | 
				
			||||||
  flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
 | 
					  flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
 | 
				
			||||||
@@ -406,8 +412,8 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
 | 
					  shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
 | 
				
			||||||
  sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
 | 
					  sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
 | 
				
			||||||
  sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
 | 
					  sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
 | 
				
			||||||
  sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
 | 
					  sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
 | 
				
			||||||
  sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
 | 
					  sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
 | 
				
			||||||
  super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
 | 
					  super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
 | 
				
			||||||
  SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
 | 
					  SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
 | 
				
			||||||
  url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
 | 
					  url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
import Foundation
 | 
					import Foundation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getAttachmentUrl(for identifier: String) -> String {
 | 
					func getAttachmentUrl(for identifier: String) -> String {
 | 
				
			||||||
    let serverBaseUrl = "https://nt.solian.app"
 | 
					    let serverBaseUrl = "https://api.solian.app"
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/files/\(identifier)"
 | 
					    return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/drive/files/\(identifier)"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,6 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface.
 | 
				
			|||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
 | 
					import 'package:flutter_native_splash/flutter_native_splash.dart';
 | 
				
			||||||
import 'package:url_launcher/url_launcher_string.dart';
 | 
					import 'package:url_launcher/url_launcher_string.dart';
 | 
				
			||||||
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
 | 
					import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
 | 
				
			||||||
import 'package:island/services/update_service.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pragma('vm:entry-point')
 | 
					@pragma('vm:entry-point')
 | 
				
			||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
 | 
					Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
 | 
				
			||||||
@@ -144,15 +143,6 @@ void main() async {
 | 
				
			|||||||
      ),
 | 
					      ),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Schedule update check shortly after startup, when a context is available.
 | 
					 | 
				
			||||||
  // Uses the global overlay key to obtain a BuildContext safely.
 | 
					 | 
				
			||||||
  WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
					 | 
				
			||||||
    final ctx = globalOverlay.currentContext;
 | 
					 | 
				
			||||||
    if (ctx != null) {
 | 
					 | 
				
			||||||
      UpdateService().checkForUpdates(ctx);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Router will be provided through Riverpod
 | 
					// Router will be provided through Riverpod
 | 
				
			||||||
@@ -181,6 +171,9 @@ class IslandApp extends HookConsumerWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() {
 | 
					    useEffect(() {
 | 
				
			||||||
 | 
					      if (!kIsWeb && Platform.isLinux) {
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      const channel = MethodChannel('dev.solsynth.solian/notifications');
 | 
					      const channel = MethodChannel('dev.solsynth.solian/notifications');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Future<void> handleInitialLink() async {
 | 
					      Future<void> handleInitialLink() async {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,25 @@
 | 
				
			|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
 | 
					import 'package:freezed_annotation/freezed_annotation.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/publisher.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part 'developer.freezed.dart';
 | 
					part 'developer.freezed.dart';
 | 
				
			||||||
part 'developer.g.dart';
 | 
					part 'developer.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class SnDeveloper with _$SnDeveloper {
 | 
				
			||||||
 | 
					  const factory SnDeveloper({
 | 
				
			||||||
 | 
					    required String id,
 | 
				
			||||||
 | 
					    required String publisherId,
 | 
				
			||||||
 | 
					    SnPublisher? publisher,
 | 
				
			||||||
 | 
					  }) = _SnDeveloper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory SnDeveloper.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$SnDeveloperFromJson(json);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@freezed
 | 
					@freezed
 | 
				
			||||||
sealed class DeveloperStats with _$DeveloperStats {
 | 
					sealed class DeveloperStats with _$DeveloperStats {
 | 
				
			||||||
  const factory DeveloperStats({
 | 
					  const factory DeveloperStats({@Default(0) int totalCustomApps}) =
 | 
				
			||||||
    @Default(0) int totalCustomApps,
 | 
					      _DeveloperStats;
 | 
				
			||||||
  }) = _DeveloperStats;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  factory DeveloperStats.fromJson(Map<String, dynamic> json) =>
 | 
					  factory DeveloperStats.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
      _$DeveloperStatsFromJson(json);
 | 
					      _$DeveloperStatsFromJson(json);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,293 @@ part of 'developer.dart';
 | 
				
			|||||||
// dart format off
 | 
					// dart format off
 | 
				
			||||||
T _$identity<T>(T value) => value;
 | 
					T _$identity<T>(T value) => value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$SnDeveloper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 String get id; String get publisherId; SnPublisher? get publisher;
 | 
				
			||||||
 | 
					/// Create a copy of SnDeveloper
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnDeveloperCopyWith<SnDeveloper> get copyWith => _$SnDeveloperCopyWithImpl<SnDeveloper>(this as SnDeveloper, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this SnDeveloper to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is SnDeveloper&&(identical(other.id, id) || other.id == id)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,publisherId,publisher);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnDeveloper(id: $id, publisherId: $publisherId, publisher: $publisher)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $SnDeveloperCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $SnDeveloperCopyWith(SnDeveloper value, $Res Function(SnDeveloper) _then) = _$SnDeveloperCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String publisherId, SnPublisher? publisher
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$SnPublisherCopyWith<$Res>? get publisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$SnDeveloperCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $SnDeveloperCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$SnDeveloperCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final SnDeveloper _self;
 | 
				
			||||||
 | 
					  final $Res Function(SnDeveloper) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnDeveloper
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? publisherId = null,Object? publisher = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnPublisher?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// Create a copy of SnDeveloper
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnPublisherCopyWith<$Res>? get publisher {
 | 
				
			||||||
 | 
					    if (_self.publisher == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(publisher: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Adds pattern-matching-related methods to [SnDeveloper].
 | 
				
			||||||
 | 
					extension SnDeveloperPatterns on SnDeveloper {
 | 
				
			||||||
 | 
					/// A variant of `map` that fallback to returning `orElse`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case final Subclass value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return orElse();
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnDeveloper value)?  $default,{required TResult orElse(),}){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnDeveloper() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that);case _:
 | 
				
			||||||
 | 
					  return orElse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A `switch`-like method, using callbacks.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Callbacks receives the raw object, upcasted.
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case final Subclass value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case final Subclass2 value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnDeveloper value)  $default,){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnDeveloper():
 | 
				
			||||||
 | 
					return $default(_that);}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A variant of `map` that fallback to returning `null`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case final Subclass value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return null;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnDeveloper value)?  $default,){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnDeveloper() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that);case _:
 | 
				
			||||||
 | 
					  return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A variant of `when` that fallback to an `orElse` callback.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case Subclass(:final field):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return orElse();
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String publisherId,  SnPublisher? publisher)?  $default,{required TResult orElse(),}) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnDeveloper() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that.id,_that.publisherId,_that.publisher);case _:
 | 
				
			||||||
 | 
					  return orElse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A `switch`-like method, using callbacks.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// As opposed to `map`, this offers destructuring.
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case Subclass(:final field):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case Subclass2(:final field2):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String publisherId,  SnPublisher? publisher)  $default,) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnDeveloper():
 | 
				
			||||||
 | 
					return $default(_that.id,_that.publisherId,_that.publisher);}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A variant of `when` that fallback to returning `null`
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case Subclass(:final field):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return null;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String publisherId,  SnPublisher? publisher)?  $default,) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _SnDeveloper() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that.id,_that.publisherId,_that.publisher);case _:
 | 
				
			||||||
 | 
					  return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SnDeveloper implements SnDeveloper {
 | 
				
			||||||
 | 
					  const _SnDeveloper({required this.id, required this.publisherId, this.publisher});
 | 
				
			||||||
 | 
					  factory _SnDeveloper.fromJson(Map<String, dynamic> json) => _$SnDeveloperFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String id;
 | 
				
			||||||
 | 
					@override final  String publisherId;
 | 
				
			||||||
 | 
					@override final  SnPublisher? publisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnDeveloper
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					_$SnDeveloperCopyWith<_SnDeveloper> get copyWith => __$SnDeveloperCopyWithImpl<_SnDeveloper>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$SnDeveloperToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnDeveloper&&(identical(other.id, id) || other.id == id)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,id,publisherId,publisher);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'SnDeveloper(id: $id, publisherId: $publisherId, publisher: $publisher)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class _$SnDeveloperCopyWith<$Res> implements $SnDeveloperCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory _$SnDeveloperCopyWith(_SnDeveloper value, $Res Function(_SnDeveloper) _then) = __$SnDeveloperCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String id, String publisherId, SnPublisher? publisher
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override $SnPublisherCopyWith<$Res>? get publisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class __$SnDeveloperCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements _$SnDeveloperCopyWith<$Res> {
 | 
				
			||||||
 | 
					  __$SnDeveloperCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final _SnDeveloper _self;
 | 
				
			||||||
 | 
					  final $Res Function(_SnDeveloper) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnDeveloper
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? publisherId = null,Object? publisher = freezed,}) {
 | 
				
			||||||
 | 
					  return _then(_SnDeveloper(
 | 
				
			||||||
 | 
					id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as SnPublisher?,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of SnDeveloper
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$SnPublisherCopyWith<$Res>? get publisher {
 | 
				
			||||||
 | 
					    if (_self.publisher == null) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) {
 | 
				
			||||||
 | 
					    return _then(_self.copyWith(publisher: value));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// @nodoc
 | 
					/// @nodoc
 | 
				
			||||||
mixin _$DeveloperStats {
 | 
					mixin _$DeveloperStats {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,22 @@ part of 'developer.dart';
 | 
				
			|||||||
// JsonSerializableGenerator
 | 
					// JsonSerializableGenerator
 | 
				
			||||||
// **************************************************************************
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_SnDeveloper _$SnDeveloperFromJson(Map<String, dynamic> json) => _SnDeveloper(
 | 
				
			||||||
 | 
					  id: json['id'] as String,
 | 
				
			||||||
 | 
					  publisherId: json['publisher_id'] as String,
 | 
				
			||||||
 | 
					  publisher:
 | 
				
			||||||
 | 
					      json['publisher'] == null
 | 
				
			||||||
 | 
					          ? null
 | 
				
			||||||
 | 
					          : SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$SnDeveloperToJson(_SnDeveloper instance) =>
 | 
				
			||||||
 | 
					    <String, dynamic>{
 | 
				
			||||||
 | 
					      'id': instance.id,
 | 
				
			||||||
 | 
					      'publisher_id': instance.publisherId,
 | 
				
			||||||
 | 
					      'publisher': instance.publisher?.toJson(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_DeveloperStats _$DeveloperStatsFromJson(Map<String, dynamic> json) =>
 | 
					_DeveloperStats _$DeveloperStatsFromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
    _DeveloperStats(
 | 
					    _DeveloperStats(
 | 
				
			||||||
      totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0,
 | 
					      totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,32 @@ sealed class SnAccount with _$SnAccount {
 | 
				
			|||||||
      _$SnAccountFromJson(json);
 | 
					      _$SnAccountFromJson(json);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@freezed
 | 
				
			||||||
 | 
					sealed class ProfileLink with _$ProfileLink {
 | 
				
			||||||
 | 
					  const factory ProfileLink({required String name, required String url}) =
 | 
				
			||||||
 | 
					      _ProfileLink;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory ProfileLink.fromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					      _$ProfileLinkFromJson(json);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProfileLinkConverter
 | 
				
			||||||
 | 
					    implements JsonConverter<List<ProfileLink>, dynamic> {
 | 
				
			||||||
 | 
					  const ProfileLinkConverter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  List<ProfileLink> fromJson(dynamic json) {
 | 
				
			||||||
 | 
					    return json is List<dynamic>
 | 
				
			||||||
 | 
					        ? json.map((e) => ProfileLink.fromJson(e)).cast<ProfileLink>().toList()
 | 
				
			||||||
 | 
					        : <ProfileLink>[];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  List<dynamic> toJson(List<ProfileLink> object) {
 | 
				
			||||||
 | 
					    return object.map((e) => e.toJson()).toList();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@freezed
 | 
					@freezed
 | 
				
			||||||
sealed class SnAccountProfile with _$SnAccountProfile {
 | 
					sealed class SnAccountProfile with _$SnAccountProfile {
 | 
				
			||||||
  const factory SnAccountProfile({
 | 
					  const factory SnAccountProfile({
 | 
				
			||||||
@@ -38,7 +64,7 @@ sealed class SnAccountProfile with _$SnAccountProfile {
 | 
				
			|||||||
    @Default('') String location,
 | 
					    @Default('') String location,
 | 
				
			||||||
    @Default('') String timeZone,
 | 
					    @Default('') String timeZone,
 | 
				
			||||||
    DateTime? birthday,
 | 
					    DateTime? birthday,
 | 
				
			||||||
    @Default({}) Map<String, String> links,
 | 
					    @ProfileLinkConverter() @Default([]) List<ProfileLink> links,
 | 
				
			||||||
    DateTime? lastSeenAt,
 | 
					    DateTime? lastSeenAt,
 | 
				
			||||||
    SnAccountBadge? activeBadge,
 | 
					    SnAccountBadge? activeBadge,
 | 
				
			||||||
    required int experience,
 | 
					    required int experience,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -347,10 +347,270 @@ $SnWalletSubscriptionRefCopyWith<$Res>? get perkSubscription {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					mixin _$ProfileLink {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 String get name; String get url;
 | 
				
			||||||
 | 
					/// Create a copy of ProfileLink
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					$ProfileLinkCopyWith<ProfileLink> get copyWith => _$ProfileLinkCopyWithImpl<ProfileLink>(this as ProfileLink, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Serializes this ProfileLink to a JSON map.
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is ProfileLink&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,name,url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'ProfileLink(name: $name, url: $url)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class $ProfileLinkCopyWith<$Res>  {
 | 
				
			||||||
 | 
					  factory $ProfileLinkCopyWith(ProfileLink value, $Res Function(ProfileLink) _then) = _$ProfileLinkCopyWithImpl;
 | 
				
			||||||
 | 
					@useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String name, String url
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class _$ProfileLinkCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements $ProfileLinkCopyWith<$Res> {
 | 
				
			||||||
 | 
					  _$ProfileLinkCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final ProfileLink _self;
 | 
				
			||||||
 | 
					  final $Res Function(ProfileLink) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of ProfileLink
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? url = null,}) {
 | 
				
			||||||
 | 
					  return _then(_self.copyWith(
 | 
				
			||||||
 | 
					name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Adds pattern-matching-related methods to [ProfileLink].
 | 
				
			||||||
 | 
					extension ProfileLinkPatterns on ProfileLink {
 | 
				
			||||||
 | 
					/// A variant of `map` that fallback to returning `orElse`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case final Subclass value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return orElse();
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ProfileLink value)?  $default,{required TResult orElse(),}){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _ProfileLink() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that);case _:
 | 
				
			||||||
 | 
					  return orElse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A `switch`-like method, using callbacks.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Callbacks receives the raw object, upcasted.
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case final Subclass value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case final Subclass2 value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ProfileLink value)  $default,){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _ProfileLink():
 | 
				
			||||||
 | 
					return $default(_that);}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A variant of `map` that fallback to returning `null`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case final Subclass value:
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return null;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ProfileLink value)?  $default,){
 | 
				
			||||||
 | 
					final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _ProfileLink() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that);case _:
 | 
				
			||||||
 | 
					  return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A variant of `when` that fallback to an `orElse` callback.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case Subclass(:final field):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return orElse();
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name,  String url)?  $default,{required TResult orElse(),}) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _ProfileLink() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that.name,_that.url);case _:
 | 
				
			||||||
 | 
					  return orElse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A `switch`-like method, using callbacks.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// As opposed to `map`, this offers destructuring.
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case Subclass(:final field):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case Subclass2(:final field2):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name,  String url)  $default,) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _ProfileLink():
 | 
				
			||||||
 | 
					return $default(_that.name,_that.url);}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// A variant of `when` that fallback to returning `null`
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// It is equivalent to doing:
 | 
				
			||||||
 | 
					/// ```dart
 | 
				
			||||||
 | 
					/// switch (sealedClass) {
 | 
				
			||||||
 | 
					///   case Subclass(:final field):
 | 
				
			||||||
 | 
					///     return ...;
 | 
				
			||||||
 | 
					///   case _:
 | 
				
			||||||
 | 
					///     return null;
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name,  String url)?  $default,) {final _that = this;
 | 
				
			||||||
 | 
					switch (_that) {
 | 
				
			||||||
 | 
					case _ProfileLink() when $default != null:
 | 
				
			||||||
 | 
					return $default(_that.name,_that.url);case _:
 | 
				
			||||||
 | 
					  return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _ProfileLink implements ProfileLink {
 | 
				
			||||||
 | 
					  const _ProfileLink({required this.name, required this.url});
 | 
				
			||||||
 | 
					  factory _ProfileLink.fromJson(Map<String, dynamic> json) => _$ProfileLinkFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override final  String name;
 | 
				
			||||||
 | 
					@override final  String url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of ProfileLink
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@pragma('vm:prefer-inline')
 | 
				
			||||||
 | 
					_$ProfileLinkCopyWith<_ProfileLink> get copyWith => __$ProfileLinkCopyWithImpl<_ProfileLink>(this, _$identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					Map<String, dynamic> toJson() {
 | 
				
			||||||
 | 
					  return _$ProfileLinkToJson(this, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					bool operator ==(Object other) {
 | 
				
			||||||
 | 
					  return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfileLink&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					int get hashCode => Object.hash(runtimeType,name,url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@override
 | 
				
			||||||
 | 
					String toString() {
 | 
				
			||||||
 | 
					  return 'ProfileLink(name: $name, url: $url)';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					abstract mixin class _$ProfileLinkCopyWith<$Res> implements $ProfileLinkCopyWith<$Res> {
 | 
				
			||||||
 | 
					  factory _$ProfileLinkCopyWith(_ProfileLink value, $Res Function(_ProfileLink) _then) = __$ProfileLinkCopyWithImpl;
 | 
				
			||||||
 | 
					@override @useResult
 | 
				
			||||||
 | 
					$Res call({
 | 
				
			||||||
 | 
					 String name, String url
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/// @nodoc
 | 
				
			||||||
 | 
					class __$ProfileLinkCopyWithImpl<$Res>
 | 
				
			||||||
 | 
					    implements _$ProfileLinkCopyWith<$Res> {
 | 
				
			||||||
 | 
					  __$ProfileLinkCopyWithImpl(this._self, this._then);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final _ProfileLink _self;
 | 
				
			||||||
 | 
					  final $Res Function(_ProfileLink) _then;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Create a copy of ProfileLink
 | 
				
			||||||
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
 | 
					@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? url = null,}) {
 | 
				
			||||||
 | 
					  return _then(_ProfileLink(
 | 
				
			||||||
 | 
					name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 | 
					as String,
 | 
				
			||||||
 | 
					  ));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// @nodoc
 | 
					/// @nodoc
 | 
				
			||||||
mixin _$SnAccountProfile {
 | 
					mixin _$SnAccountProfile {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday; Map<String, String> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
 | 
					 String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday;@ProfileLinkConverter() List<ProfileLink> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
 | 
				
			||||||
/// Create a copy of SnAccountProfile
 | 
					/// Create a copy of SnAccountProfile
 | 
				
			||||||
/// with the given fields replaced by the non-null parameter values.
 | 
					/// with the given fields replaced by the non-null parameter values.
 | 
				
			||||||
@JsonKey(includeFromJson: false, includeToJson: false)
 | 
					@JsonKey(includeFromJson: false, includeToJson: false)
 | 
				
			||||||
@@ -383,7 +643,7 @@ abstract mixin class $SnAccountProfileCopyWith<$Res>  {
 | 
				
			|||||||
  factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
 | 
					  factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
 | 
				
			||||||
@useResult
 | 
					@useResult
 | 
				
			||||||
$Res call({
 | 
					$Res call({
 | 
				
			||||||
 String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, Map<String, String> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
					 String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -413,7 +673,7 @@ as String,location: null == location ? _self.location : location // ignore: cast
 | 
				
			|||||||
as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
 | 
					as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
 | 
					as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime?,links: null == links ? _self.links : links // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime?,links: null == links ? _self.links : links // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as Map<String, String>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
 | 
					as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
 | 
					as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
 | 
					as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
@@ -554,7 +814,7 @@ return $default(_that);case _:
 | 
				
			|||||||
/// }
 | 
					/// }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday,  Map<String, String> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this;
 | 
					@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday, @ProfileLinkConverter()  List<ProfileLink> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this;
 | 
				
			||||||
switch (_that) {
 | 
					switch (_that) {
 | 
				
			||||||
case _SnAccountProfile() when $default != null:
 | 
					case _SnAccountProfile() when $default != null:
 | 
				
			||||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
 | 
					return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
 | 
				
			||||||
@@ -575,7 +835,7 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
 | 
				
			|||||||
/// }
 | 
					/// }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday,  Map<String, String> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this;
 | 
					@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday, @ProfileLinkConverter()  List<ProfileLink> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this;
 | 
				
			||||||
switch (_that) {
 | 
					switch (_that) {
 | 
				
			||||||
case _SnAccountProfile():
 | 
					case _SnAccountProfile():
 | 
				
			||||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);}
 | 
					return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);}
 | 
				
			||||||
@@ -592,7 +852,7 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
 | 
				
			|||||||
/// }
 | 
					/// }
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday,  Map<String, String> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this;
 | 
					@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String firstName,  String middleName,  String lastName,  String bio,  String gender,  String pronouns,  String location,  String timeZone,  DateTime? birthday, @ProfileLinkConverter()  List<ProfileLink> links,  DateTime? lastSeenAt,  SnAccountBadge? activeBadge,  int experience,  int level,  double levelingProgress,  SnCloudFile? picture,  SnCloudFile? background,  SnVerificationMark? verification,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this;
 | 
				
			||||||
switch (_that) {
 | 
					switch (_that) {
 | 
				
			||||||
case _SnAccountProfile() when $default != null:
 | 
					case _SnAccountProfile() when $default != null:
 | 
				
			||||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
 | 
					return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
 | 
				
			||||||
@@ -607,7 +867,7 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
 | 
				
			|||||||
@JsonSerializable()
 | 
					@JsonSerializable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _SnAccountProfile implements SnAccountProfile {
 | 
					class _SnAccountProfile implements SnAccountProfile {
 | 
				
			||||||
  const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, final  Map<String, String> links = const {}, this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links;
 | 
					  const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, @ProfileLinkConverter() final  List<ProfileLink> links = const [], this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links;
 | 
				
			||||||
  factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
 | 
					  factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@override final  String id;
 | 
					@override final  String id;
 | 
				
			||||||
@@ -620,11 +880,11 @@ class _SnAccountProfile implements SnAccountProfile {
 | 
				
			|||||||
@override@JsonKey() final  String location;
 | 
					@override@JsonKey() final  String location;
 | 
				
			||||||
@override@JsonKey() final  String timeZone;
 | 
					@override@JsonKey() final  String timeZone;
 | 
				
			||||||
@override final  DateTime? birthday;
 | 
					@override final  DateTime? birthday;
 | 
				
			||||||
 final  Map<String, String> _links;
 | 
					 final  List<ProfileLink> _links;
 | 
				
			||||||
@override@JsonKey() Map<String, String> get links {
 | 
					@override@JsonKey()@ProfileLinkConverter() List<ProfileLink> get links {
 | 
				
			||||||
  if (_links is EqualUnmodifiableMapView) return _links;
 | 
					  if (_links is EqualUnmodifiableListView) return _links;
 | 
				
			||||||
  // ignore: implicit_dynamic_type
 | 
					  // ignore: implicit_dynamic_type
 | 
				
			||||||
  return EqualUnmodifiableMapView(_links);
 | 
					  return EqualUnmodifiableListView(_links);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@override final  DateTime? lastSeenAt;
 | 
					@override final  DateTime? lastSeenAt;
 | 
				
			||||||
@@ -672,7 +932,7 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi
 | 
				
			|||||||
  factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
 | 
					  factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
 | 
				
			||||||
@override @useResult
 | 
					@override @useResult
 | 
				
			||||||
$Res call({
 | 
					$Res call({
 | 
				
			||||||
 String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, Map<String, String> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
					 String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -702,7 +962,7 @@ as String,location: null == location ? _self.location : location // ignore: cast
 | 
				
			|||||||
as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
 | 
					as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
 | 
					as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime?,links: null == links ? _self._links : links // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime?,links: null == links ? _self._links : links // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as Map<String, String>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
 | 
					as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
 | 
					as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
 | 
					as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
 | 
					as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,12 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) =>
 | 
				
			|||||||
      'deleted_at': instance.deletedAt?.toIso8601String(),
 | 
					      'deleted_at': instance.deletedAt?.toIso8601String(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_ProfileLink _$ProfileLinkFromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
 | 
					    _ProfileLink(name: json['name'] as String, url: json['url'] as String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Map<String, dynamic> _$ProfileLinkToJson(_ProfileLink instance) =>
 | 
				
			||||||
 | 
					    <String, dynamic>{'name': instance.name, 'url': instance.url};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
 | 
					_SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
 | 
				
			||||||
    _SnAccountProfile(
 | 
					    _SnAccountProfile(
 | 
				
			||||||
      id: json['id'] as String,
 | 
					      id: json['id'] as String,
 | 
				
			||||||
@@ -63,10 +69,9 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
 | 
				
			|||||||
              ? null
 | 
					              ? null
 | 
				
			||||||
              : DateTime.parse(json['birthday'] as String),
 | 
					              : DateTime.parse(json['birthday'] as String),
 | 
				
			||||||
      links:
 | 
					      links:
 | 
				
			||||||
          (json['links'] as Map<String, dynamic>?)?.map(
 | 
					          json['links'] == null
 | 
				
			||||||
            (k, e) => MapEntry(k, e as String),
 | 
					              ? const []
 | 
				
			||||||
          ) ??
 | 
					              : const ProfileLinkConverter().fromJson(json['links']),
 | 
				
			||||||
          const {},
 | 
					 | 
				
			||||||
      lastSeenAt:
 | 
					      lastSeenAt:
 | 
				
			||||||
          json['last_seen_at'] == null
 | 
					          json['last_seen_at'] == null
 | 
				
			||||||
              ? null
 | 
					              ? null
 | 
				
			||||||
@@ -116,7 +121,7 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
 | 
				
			|||||||
      'location': instance.location,
 | 
					      'location': instance.location,
 | 
				
			||||||
      'time_zone': instance.timeZone,
 | 
					      'time_zone': instance.timeZone,
 | 
				
			||||||
      'birthday': instance.birthday?.toIso8601String(),
 | 
					      'birthday': instance.birthday?.toIso8601String(),
 | 
				
			||||||
      'links': instance.links,
 | 
					      'links': const ProfileLinkConverter().toJson(instance.links),
 | 
				
			||||||
      'last_seen_at': instance.lastSeenAt?.toIso8601String(),
 | 
					      'last_seen_at': instance.lastSeenAt?.toIso8601String(),
 | 
				
			||||||
      'active_badge': instance.activeBadge?.toJson(),
 | 
					      'active_badge': instance.activeBadge?.toJson(),
 | 
				
			||||||
      'experience': instance.experience,
 | 
					      'experience': instance.experience,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,12 +7,12 @@ import 'package:flutter/services.dart';
 | 
				
			|||||||
import 'package:gap/gap.dart';
 | 
					import 'package:gap/gap.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:island/services/udid.native.dart';
 | 
					import 'package:island/services/udid.native.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/alert.dart';
 | 
				
			||||||
import 'package:island/widgets/app_scaffold.dart';
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
import 'package:material_symbols_icons/symbols.dart';
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
import 'package:package_info_plus/package_info_plus.dart';
 | 
					import 'package:package_info_plus/package_info_plus.dart';
 | 
				
			||||||
import 'package:styled_widget/styled_widget.dart';
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
import 'package:island/services/update_service.dart';
 | 
					import 'package:island/services/update_service.dart';
 | 
				
			||||||
import 'package:island/widgets/content/sheet.dart';
 | 
					 | 
				
			||||||
import 'package:url_launcher/url_launcher.dart';
 | 
					import 'package:url_launcher/url_launcher.dart';
 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:url_launcher/url_launcher_string.dart';
 | 
					import 'package:url_launcher/url_launcher_string.dart';
 | 
				
			||||||
@@ -102,235 +102,226 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
 | 
				
			|||||||
              ? const Center(child: CircularProgressIndicator())
 | 
					              ? const Center(child: CircularProgressIndicator())
 | 
				
			||||||
              : _errorMessage != null
 | 
					              : _errorMessage != null
 | 
				
			||||||
              ? Center(child: Text(_errorMessage!))
 | 
					              ? Center(child: Text(_errorMessage!))
 | 
				
			||||||
              : SingleChildScrollView(
 | 
					              : Center(
 | 
				
			||||||
                child: Column(
 | 
					                child: ConstrainedBox(
 | 
				
			||||||
                  crossAxisAlignment: CrossAxisAlignment.center,
 | 
					                  constraints: const BoxConstraints(maxWidth: 540),
 | 
				
			||||||
                  children: [
 | 
					                  child: SingleChildScrollView(
 | 
				
			||||||
                    const SizedBox(height: 24),
 | 
					                    child: Column(
 | 
				
			||||||
                    // App Icon and Name
 | 
					                      crossAxisAlignment: CrossAxisAlignment.center,
 | 
				
			||||||
                    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(
 | 
					 | 
				
			||||||
                      'aboutScreenVersionInfo'.tr(
 | 
					 | 
				
			||||||
                        args: [_packageInfo.version, _packageInfo.buildNumber],
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      style: theme.textTheme.bodyMedium?.copyWith(
 | 
					 | 
				
			||||||
                        color: theme.textTheme.bodySmall?.color,
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    const SizedBox(height: 32),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // App Info Card
 | 
					 | 
				
			||||||
                    _buildSection(
 | 
					 | 
				
			||||||
                      context,
 | 
					 | 
				
			||||||
                      title: 'aboutScreenAppInfoSectionTitle'.tr(),
 | 
					 | 
				
			||||||
                      children: [
 | 
					                      children: [
 | 
				
			||||||
                        _buildInfoItem(
 | 
					                        const SizedBox(height: 24),
 | 
				
			||||||
                          context,
 | 
					                        // App Icon and Name
 | 
				
			||||||
                          icon: Symbols.info,
 | 
					                        CircleAvatar(
 | 
				
			||||||
                          label: 'aboutScreenPackageNameLabel'.tr(),
 | 
					                          radius: 50,
 | 
				
			||||||
                          value: _packageInfo.packageName,
 | 
					                          backgroundColor: theme.colorScheme.primary
 | 
				
			||||||
                        ),
 | 
					                              .withOpacity(0.1),
 | 
				
			||||||
                        _buildInfoItem(
 | 
					                          child: Image.asset(
 | 
				
			||||||
                          context,
 | 
					                            'assets/icons/icon.png',
 | 
				
			||||||
                          icon: Symbols.update,
 | 
					                            width: 56,
 | 
				
			||||||
                          label: 'aboutScreenVersionLabel'.tr(),
 | 
					                            height: 56,
 | 
				
			||||||
                          value: _packageInfo.version,
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        _buildInfoItem(
 | 
					 | 
				
			||||||
                          context,
 | 
					 | 
				
			||||||
                          icon: Symbols.build,
 | 
					 | 
				
			||||||
                          label: 'aboutScreenBuildNumberLabel'.tr(),
 | 
					 | 
				
			||||||
                          value: _packageInfo.buildNumber,
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ],
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (_deviceInfo != null) const SizedBox(height: 16),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (_deviceInfo != null)
 | 
					 | 
				
			||||||
                      _buildSection(
 | 
					 | 
				
			||||||
                        context,
 | 
					 | 
				
			||||||
                        title: 'Device Information',
 | 
					 | 
				
			||||||
                        children: [
 | 
					 | 
				
			||||||
                          _buildInfoItem(
 | 
					 | 
				
			||||||
                            context,
 | 
					 | 
				
			||||||
                            icon: Symbols.label,
 | 
					 | 
				
			||||||
                            label: 'aboutDeviceName'.tr(),
 | 
					 | 
				
			||||||
                            value: _deviceInfo?.data['name'],
 | 
					 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                          _buildInfoItem(
 | 
					                        ),
 | 
				
			||||||
                            context,
 | 
					                        const SizedBox(height: 16),
 | 
				
			||||||
                            icon: Symbols.fingerprint,
 | 
					                        Text(
 | 
				
			||||||
                            label: 'aboutDeviceIdentifier'.tr(),
 | 
					                          _packageInfo.appName,
 | 
				
			||||||
                            value: _deviceUdid ?? 'N/A',
 | 
					                          style: theme.textTheme.headlineSmall?.copyWith(
 | 
				
			||||||
                            copyable: true,
 | 
					                            fontWeight: FontWeight.bold,
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                        ],
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const SizedBox(height: 16),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Links Card
 | 
					 | 
				
			||||||
                    _buildSection(
 | 
					 | 
				
			||||||
                      context,
 | 
					 | 
				
			||||||
                      title: 'aboutScreenLinksSectionTitle'.tr(),
 | 
					 | 
				
			||||||
                      children: [
 | 
					 | 
				
			||||||
                        _buildListTile(
 | 
					 | 
				
			||||||
                          context,
 | 
					 | 
				
			||||||
                          icon: Symbols.system_update,
 | 
					 | 
				
			||||||
                          title: 'Check for updates',
 | 
					 | 
				
			||||||
                          onTap: () async {
 | 
					 | 
				
			||||||
                            // Fetch latest release and show the unified sheet
 | 
					 | 
				
			||||||
                            final svc = UpdateService();
 | 
					 | 
				
			||||||
                            // Reuse service fetch + compare to decide content
 | 
					 | 
				
			||||||
                            final release = await svc.fetchLatestRelease();
 | 
					 | 
				
			||||||
                            if (release != null) {
 | 
					 | 
				
			||||||
                              await svc.showUpdateSheet(context, release);
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                              // Fallback: show a simple sheet indicating no info
 | 
					 | 
				
			||||||
                              // Use your SheetScaffold for consistent styling
 | 
					 | 
				
			||||||
                              // Show a minimal message
 | 
					 | 
				
			||||||
                              // ignore: use_build_context_synchronously
 | 
					 | 
				
			||||||
                              showModalBottomSheet(
 | 
					 | 
				
			||||||
                                context: context,
 | 
					 | 
				
			||||||
                                isScrollControlled: true,
 | 
					 | 
				
			||||||
                                useSafeArea: true,
 | 
					 | 
				
			||||||
                                showDragHandle: true,
 | 
					 | 
				
			||||||
                                backgroundColor:
 | 
					 | 
				
			||||||
                                    Theme.of(context).colorScheme.surface,
 | 
					 | 
				
			||||||
                                builder:
 | 
					 | 
				
			||||||
                                    (_) => const SheetScaffold(
 | 
					 | 
				
			||||||
                                      titleText: 'Update',
 | 
					 | 
				
			||||||
                                      child: Center(
 | 
					 | 
				
			||||||
                                        child: Padding(
 | 
					 | 
				
			||||||
                                          padding: EdgeInsets.all(24),
 | 
					 | 
				
			||||||
                                          child: Text(
 | 
					 | 
				
			||||||
                                            'Unable to fetch release info at this time.',
 | 
					 | 
				
			||||||
                                          ),
 | 
					 | 
				
			||||||
                                        ),
 | 
					 | 
				
			||||||
                                      ),
 | 
					 | 
				
			||||||
                                    ),
 | 
					 | 
				
			||||||
                              );
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                          },
 | 
					 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                        _buildListTile(
 | 
					                        Text(
 | 
				
			||||||
                          context,
 | 
					                          'aboutScreenVersionInfo'.tr(
 | 
				
			||||||
                          icon: Symbols.privacy_tip,
 | 
					                            args: [
 | 
				
			||||||
                          title: 'aboutScreenPrivacyPolicyTitle'.tr(),
 | 
					                              _packageInfo.version,
 | 
				
			||||||
                          onTap:
 | 
					                              _packageInfo.buildNumber,
 | 
				
			||||||
                              () => _launchURL(
 | 
					                            ],
 | 
				
			||||||
                                'https://solsynth.dev/terms/privacy-policy',
 | 
					 | 
				
			||||||
                              ),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        _buildListTile(
 | 
					 | 
				
			||||||
                          context,
 | 
					 | 
				
			||||||
                          icon: Symbols.description,
 | 
					 | 
				
			||||||
                          title: 'aboutScreenTermsOfServiceTitle'.tr(),
 | 
					 | 
				
			||||||
                          onTap:
 | 
					 | 
				
			||||||
                              () => _launchURL(
 | 
					 | 
				
			||||||
                                'https://solsynth.dev/terms/user-agreement',
 | 
					 | 
				
			||||||
                              ),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        _buildListTile(
 | 
					 | 
				
			||||||
                          context,
 | 
					 | 
				
			||||||
                          icon: Symbols.code,
 | 
					 | 
				
			||||||
                          title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
 | 
					 | 
				
			||||||
                          onTap: () {
 | 
					 | 
				
			||||||
                            showLicensePage(
 | 
					 | 
				
			||||||
                              context: context,
 | 
					 | 
				
			||||||
                              applicationName: _packageInfo.appName,
 | 
					 | 
				
			||||||
                              applicationVersion:
 | 
					 | 
				
			||||||
                                  'Version ${_packageInfo.version}',
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
                          },
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ],
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const SizedBox(height: 16),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Developer Info
 | 
					 | 
				
			||||||
                    _buildSection(
 | 
					 | 
				
			||||||
                      context,
 | 
					 | 
				
			||||||
                      title: 'aboutScreenDeveloperSectionTitle'.tr(),
 | 
					 | 
				
			||||||
                      children: [
 | 
					 | 
				
			||||||
                        _buildListTile(
 | 
					 | 
				
			||||||
                          context,
 | 
					 | 
				
			||||||
                          icon: Symbols.email,
 | 
					 | 
				
			||||||
                          title: 'aboutScreenContactUsTitle'.tr(),
 | 
					 | 
				
			||||||
                          subtitle: 'lily@solsynth.dev',
 | 
					 | 
				
			||||||
                          onTap: () => _launchURL('mailto:lily@solsynth.dev'),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        _buildListTile(
 | 
					 | 
				
			||||||
                          context,
 | 
					 | 
				
			||||||
                          icon: Symbols.copyright,
 | 
					 | 
				
			||||||
                          title: 'aboutScreenLicenseTitle'.tr(),
 | 
					 | 
				
			||||||
                          subtitle: 'aboutScreenLicenseContent'.tr(
 | 
					 | 
				
			||||||
                            args: [DateTime.now().year.toString()],
 | 
					 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                          onTap:
 | 
					                          style: theme.textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
                              () => _launchURL(
 | 
					                            color: theme.textTheme.bodySmall?.color,
 | 
				
			||||||
                                'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
 | 
					 | 
				
			||||||
                              ),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        if (kIsWeb || !(Platform.isMacOS || Platform.isIOS))
 | 
					 | 
				
			||||||
                          _buildListTile(
 | 
					 | 
				
			||||||
                            context,
 | 
					 | 
				
			||||||
                            icon: Symbols.favorite,
 | 
					 | 
				
			||||||
                            title: 'donate'.tr(),
 | 
					 | 
				
			||||||
                            subtitle: 'donateDescription'.tr(),
 | 
					 | 
				
			||||||
                            onTap: () {
 | 
					 | 
				
			||||||
                              launchUrlString(
 | 
					 | 
				
			||||||
                                'https://afdian.com/@littlesheep',
 | 
					 | 
				
			||||||
                              );
 | 
					 | 
				
			||||||
                            },
 | 
					 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                      ],
 | 
					                        ),
 | 
				
			||||||
                    ),
 | 
					                        const SizedBox(height: 32),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    const SizedBox(height: 32),
 | 
					                        // App Info Card
 | 
				
			||||||
 | 
					                        _buildSection(
 | 
				
			||||||
                    // Copyright
 | 
					                          context,
 | 
				
			||||||
                    Padding(
 | 
					                          title: 'aboutScreenAppInfoSectionTitle'.tr(),
 | 
				
			||||||
                      padding: const EdgeInsets.all(16.0),
 | 
					                          children: [
 | 
				
			||||||
                      child: Column(
 | 
					                            _buildInfoItem(
 | 
				
			||||||
                        children: [
 | 
					                              context,
 | 
				
			||||||
                          Text(
 | 
					                              icon: Symbols.info,
 | 
				
			||||||
                            'aboutScreenCopyright'.tr(
 | 
					                              label: 'aboutScreenPackageNameLabel'.tr(),
 | 
				
			||||||
                              args: [DateTime.now().year.toString()],
 | 
					                              value: _packageInfo.packageName,
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            style: theme.textTheme.bodySmall,
 | 
					                            _buildInfoItem(
 | 
				
			||||||
                            textAlign: TextAlign.center,
 | 
					                              context,
 | 
				
			||||||
                          ),
 | 
					                              icon: Symbols.update,
 | 
				
			||||||
                          const Gap(1),
 | 
					                              label: 'aboutScreenVersionLabel'.tr(),
 | 
				
			||||||
                          Text(
 | 
					                              value: _packageInfo.version,
 | 
				
			||||||
                            'aboutScreenMadeWith'.tr(),
 | 
					                            ),
 | 
				
			||||||
                            textAlign: TextAlign.center,
 | 
					                            _buildInfoItem(
 | 
				
			||||||
                          ).fontSize(10).opacity(0.8),
 | 
					                              context,
 | 
				
			||||||
                        ],
 | 
					                              icon: Symbols.build,
 | 
				
			||||||
                      ),
 | 
					                              label: 'aboutScreenBuildNumberLabel'.tr(),
 | 
				
			||||||
                    ),
 | 
					                              value: _packageInfo.buildNumber,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ],
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    Gap(MediaQuery.of(context).padding.bottom + 16),
 | 
					                        if (_deviceInfo != null) const SizedBox(height: 16),
 | 
				
			||||||
                  ],
 | 
					
 | 
				
			||||||
 | 
					                        if (_deviceInfo != null)
 | 
				
			||||||
 | 
					                          _buildSection(
 | 
				
			||||||
 | 
					                            context,
 | 
				
			||||||
 | 
					                            title: 'Device Information',
 | 
				
			||||||
 | 
					                            children: [
 | 
				
			||||||
 | 
					                              _buildInfoItem(
 | 
				
			||||||
 | 
					                                context,
 | 
				
			||||||
 | 
					                                icon: Symbols.label,
 | 
				
			||||||
 | 
					                                label: 'aboutDeviceName'.tr(),
 | 
				
			||||||
 | 
					                                value: _deviceInfo?.data['name'],
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                              _buildInfoItem(
 | 
				
			||||||
 | 
					                                context,
 | 
				
			||||||
 | 
					                                icon: Symbols.fingerprint,
 | 
				
			||||||
 | 
					                                label: 'aboutDeviceIdentifier'.tr(),
 | 
				
			||||||
 | 
					                                value: _deviceUdid ?? 'N/A',
 | 
				
			||||||
 | 
					                                copyable: true,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            ],
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        const SizedBox(height: 16),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Links Card
 | 
				
			||||||
 | 
					                        _buildSection(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          title: 'aboutScreenLinksSectionTitle'.tr(),
 | 
				
			||||||
 | 
					                          children: [
 | 
				
			||||||
 | 
					                            _buildListTile(
 | 
				
			||||||
 | 
					                              context,
 | 
				
			||||||
 | 
					                              icon: Symbols.system_update,
 | 
				
			||||||
 | 
					                              title: 'Check for updates',
 | 
				
			||||||
 | 
					                              onTap: () async {
 | 
				
			||||||
 | 
					                                // Fetch latest release and show the unified sheet
 | 
				
			||||||
 | 
					                                final svc = UpdateService();
 | 
				
			||||||
 | 
					                                // Reuse service fetch + compare to decide content
 | 
				
			||||||
 | 
					                                showLoadingModal(context);
 | 
				
			||||||
 | 
					                                final release = await svc.fetchLatestRelease();
 | 
				
			||||||
 | 
					                                if (!context.mounted) return;
 | 
				
			||||||
 | 
					                                hideLoadingModal(context);
 | 
				
			||||||
 | 
					                                if (release != null) {
 | 
				
			||||||
 | 
					                                  await svc.showUpdateSheet(context, release);
 | 
				
			||||||
 | 
					                                } else {
 | 
				
			||||||
 | 
					                                  showInfoAlert(
 | 
				
			||||||
 | 
					                                    'Currently cannot get update from the GitHub.',
 | 
				
			||||||
 | 
					                                    'Unable to check for updates',
 | 
				
			||||||
 | 
					                                  );
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                              },
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            _buildListTile(
 | 
				
			||||||
 | 
					                              context,
 | 
				
			||||||
 | 
					                              icon: Symbols.privacy_tip,
 | 
				
			||||||
 | 
					                              title: 'aboutScreenPrivacyPolicyTitle'.tr(),
 | 
				
			||||||
 | 
					                              onTap:
 | 
				
			||||||
 | 
					                                  () => _launchURL(
 | 
				
			||||||
 | 
					                                    'https://solsynth.dev/terms/privacy-policy',
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            _buildListTile(
 | 
				
			||||||
 | 
					                              context,
 | 
				
			||||||
 | 
					                              icon: Symbols.description,
 | 
				
			||||||
 | 
					                              title: 'aboutScreenTermsOfServiceTitle'.tr(),
 | 
				
			||||||
 | 
					                              onTap:
 | 
				
			||||||
 | 
					                                  () => _launchURL(
 | 
				
			||||||
 | 
					                                    'https://solsynth.dev/terms/user-agreement',
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            _buildListTile(
 | 
				
			||||||
 | 
					                              context,
 | 
				
			||||||
 | 
					                              icon: Symbols.code,
 | 
				
			||||||
 | 
					                              title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
 | 
				
			||||||
 | 
					                              onTap: () {
 | 
				
			||||||
 | 
					                                showLicensePage(
 | 
				
			||||||
 | 
					                                  context: context,
 | 
				
			||||||
 | 
					                                  applicationName: _packageInfo.appName,
 | 
				
			||||||
 | 
					                                  applicationVersion:
 | 
				
			||||||
 | 
					                                      'Version ${_packageInfo.version}',
 | 
				
			||||||
 | 
					                                );
 | 
				
			||||||
 | 
					                              },
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ],
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        const SizedBox(height: 16),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Developer Info
 | 
				
			||||||
 | 
					                        _buildSection(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          title: 'aboutScreenDeveloperSectionTitle'.tr(),
 | 
				
			||||||
 | 
					                          children: [
 | 
				
			||||||
 | 
					                            _buildListTile(
 | 
				
			||||||
 | 
					                              context,
 | 
				
			||||||
 | 
					                              icon: Symbols.email,
 | 
				
			||||||
 | 
					                              title: 'aboutScreenContactUsTitle'.tr(),
 | 
				
			||||||
 | 
					                              subtitle: 'lily@solsynth.dev',
 | 
				
			||||||
 | 
					                              onTap:
 | 
				
			||||||
 | 
					                                  () => _launchURL('mailto:lily@solsynth.dev'),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            _buildListTile(
 | 
				
			||||||
 | 
					                              context,
 | 
				
			||||||
 | 
					                              icon: Symbols.copyright,
 | 
				
			||||||
 | 
					                              title: 'aboutScreenLicenseTitle'.tr(),
 | 
				
			||||||
 | 
					                              subtitle: 'aboutScreenLicenseContent'.tr(
 | 
				
			||||||
 | 
					                                args: [DateTime.now().year.toString()],
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                              onTap:
 | 
				
			||||||
 | 
					                                  () => _launchURL(
 | 
				
			||||||
 | 
					                                    'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            if (kIsWeb || !(Platform.isMacOS || Platform.isIOS))
 | 
				
			||||||
 | 
					                              _buildListTile(
 | 
				
			||||||
 | 
					                                context,
 | 
				
			||||||
 | 
					                                icon: Symbols.favorite,
 | 
				
			||||||
 | 
					                                title: 'donate'.tr(),
 | 
				
			||||||
 | 
					                                subtitle: 'donateDescription'.tr(),
 | 
				
			||||||
 | 
					                                onTap: () {
 | 
				
			||||||
 | 
					                                  launchUrlString(
 | 
				
			||||||
 | 
					                                    'https://afdian.com/@littlesheep',
 | 
				
			||||||
 | 
					                                  );
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                          ],
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        const SizedBox(height: 32),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Copyright
 | 
				
			||||||
 | 
					                        Padding(
 | 
				
			||||||
 | 
					                          padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
 | 
					                          child: Column(
 | 
				
			||||||
 | 
					                            children: [
 | 
				
			||||||
 | 
					                              Text(
 | 
				
			||||||
 | 
					                                'aboutScreenCopyright'.tr(
 | 
				
			||||||
 | 
					                                  args: [DateTime.now().year.toString()],
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                                style: theme.textTheme.bodySmall,
 | 
				
			||||||
 | 
					                                textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                              const Gap(1),
 | 
				
			||||||
 | 
					                              Text(
 | 
				
			||||||
 | 
					                                'aboutScreenMadeWith'.tr(),
 | 
				
			||||||
 | 
					                                textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                              ).fontSize(10).opacity(0.8),
 | 
				
			||||||
 | 
					                            ],
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Gap(MediaQuery.of(context).padding.bottom + 16),
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,8 @@
 | 
				
			|||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/foundation.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:go_router/go_router.dart';
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					 | 
				
			||||||
import 'package:gap/gap.dart';
 | 
					import 'package:gap/gap.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:island/pods/message.dart';
 | 
					 | 
				
			||||||
import 'package:island/pods/network.dart';
 | 
					 | 
				
			||||||
import 'package:island/pods/userinfo.dart';
 | 
					import 'package:island/pods/userinfo.dart';
 | 
				
			||||||
import 'package:island/screens/notification.dart';
 | 
					import 'package:island/screens/notification.dart';
 | 
				
			||||||
import 'package:island/services/responsive.dart';
 | 
					import 'package:island/services/responsive.dart';
 | 
				
			||||||
@@ -15,6 +11,7 @@ import 'package:island/widgets/account/status.dart';
 | 
				
			|||||||
import 'package:island/widgets/account/leveling_progress.dart';
 | 
					import 'package:island/widgets/account/leveling_progress.dart';
 | 
				
			||||||
import 'package:island/widgets/app_scaffold.dart';
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
import 'package:island/widgets/content/cloud_files.dart';
 | 
					import 'package:island/widgets/content/cloud_files.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/debug_sheet.dart';
 | 
				
			||||||
import 'package:material_symbols_icons/symbols.dart';
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
import 'package:styled_widget/styled_widget.dart';
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -276,30 +273,6 @@ class AccountScreen extends HookConsumerWidget {
 | 
				
			|||||||
                context.pushNamed('accountSettings');
 | 
					                context.pushNamed('accountSettings');
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            if (kDebugMode) const Divider(height: 1).padding(vertical: 8),
 | 
					 | 
				
			||||||
            if (kDebugMode)
 | 
					 | 
				
			||||||
              ListTile(
 | 
					 | 
				
			||||||
                minTileHeight: 48,
 | 
					 | 
				
			||||||
                leading: const Icon(Symbols.copy_all),
 | 
					 | 
				
			||||||
                trailing: const Icon(Symbols.chevron_right),
 | 
					 | 
				
			||||||
                contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
					 | 
				
			||||||
                title: Text('Copy access token'),
 | 
					 | 
				
			||||||
                onTap: () async {
 | 
					 | 
				
			||||||
                  final tk = ref.watch(tokenProvider);
 | 
					 | 
				
			||||||
                  Clipboard.setData(ClipboardData(text: tk!.token));
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            if (kDebugMode)
 | 
					 | 
				
			||||||
              ListTile(
 | 
					 | 
				
			||||||
                minTileHeight: 48,
 | 
					 | 
				
			||||||
                leading: const Icon(Symbols.delete),
 | 
					 | 
				
			||||||
                trailing: const Icon(Symbols.chevron_right),
 | 
					 | 
				
			||||||
                contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
					 | 
				
			||||||
                title: Text('Reset database'),
 | 
					 | 
				
			||||||
                onTap: () async {
 | 
					 | 
				
			||||||
                  resetDatabase(ref);
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            const Divider(height: 1).padding(vertical: 8),
 | 
					            const Divider(height: 1).padding(vertical: 8),
 | 
				
			||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              minTileHeight: 48,
 | 
					              minTileHeight: 48,
 | 
				
			||||||
@@ -311,6 +284,19 @@ class AccountScreen extends HookConsumerWidget {
 | 
				
			|||||||
                context.pushNamed('about');
 | 
					                context.pushNamed('about');
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            ListTile(
 | 
				
			||||||
 | 
					              minTileHeight: 48,
 | 
				
			||||||
 | 
					              leading: const Icon(Symbols.bug_report),
 | 
				
			||||||
 | 
					              trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					              contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					              title: Text('debugOptions').tr(),
 | 
				
			||||||
 | 
					              onTap: () {
 | 
				
			||||||
 | 
					                showModalBottomSheet(
 | 
				
			||||||
 | 
					                  context: context,
 | 
				
			||||||
 | 
					                  builder: (context) => DebugSheet(),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            ListTile(
 | 
					            ListTile(
 | 
				
			||||||
              minTileHeight: 48,
 | 
					              minTileHeight: 48,
 | 
				
			||||||
              leading: const Icon(Symbols.logout),
 | 
					              leading: const Icon(Symbols.logout),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import 'package:gap/gap.dart';
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:image_picker/image_picker.dart';
 | 
					import 'package:image_picker/image_picker.dart';
 | 
				
			||||||
import 'package:island/models/file.dart';
 | 
					import 'package:island/models/file.dart';
 | 
				
			||||||
 | 
					import 'package:island/models/user.dart';
 | 
				
			||||||
import 'package:island/pods/config.dart';
 | 
					import 'package:island/pods/config.dart';
 | 
				
			||||||
import 'package:island/pods/network.dart';
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
import 'package:island/pods/userinfo.dart';
 | 
					import 'package:island/pods/userinfo.dart';
 | 
				
			||||||
@@ -95,11 +96,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
    final usernameController = useTextEditingController(text: user.value!.name);
 | 
					    final usernameController = useTextEditingController(text: user.value!.name);
 | 
				
			||||||
    final nicknameController = useTextEditingController(text: user.value!.nick);
 | 
					    final nicknameController = useTextEditingController(text: user.value!.nick);
 | 
				
			||||||
    final language = useState(user.value!.language);
 | 
					    final language = useState(user.value!.language);
 | 
				
			||||||
    final links = useState<List<Map<String, String>>>(
 | 
					    final links = useState<List<ProfileLink>>(user.value!.profile.links);
 | 
				
			||||||
      user.value!.profile.links.entries
 | 
					 | 
				
			||||||
          .map((e) => {'key': e.key, 'value': e.value})
 | 
					 | 
				
			||||||
          .toList(),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void updateBasicInfo() async {
 | 
					    void updateBasicInfo() async {
 | 
				
			||||||
      if (!formKeyBasicInfo.currentState!.validate()) return;
 | 
					      if (!formKeyBasicInfo.currentState!.validate()) return;
 | 
				
			||||||
@@ -171,7 +168,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
            'location': locationController.text,
 | 
					            'location': locationController.text,
 | 
				
			||||||
            'time_zone': timeZoneController.text,
 | 
					            'time_zone': timeZoneController.text,
 | 
				
			||||||
            'birthday': birthday.value?.toUtc().toIso8601String(),
 | 
					            'birthday': birthday.value?.toUtc().toIso8601String(),
 | 
				
			||||||
            'links': {for (var e in links.value) e['key']!: e['value']!},
 | 
					            'links': links.value,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        final userNotifier = ref.read(userInfoProvider.notifier);
 | 
					        final userNotifier = ref.read(userInfoProvider.notifier);
 | 
				
			||||||
@@ -575,13 +572,15 @@ class UpdateProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                          children: [
 | 
					                          children: [
 | 
				
			||||||
                            Expanded(
 | 
					                            Expanded(
 | 
				
			||||||
                              child: TextFormField(
 | 
					                              child: TextFormField(
 | 
				
			||||||
                                initialValue: links.value[i]['key'],
 | 
					                                initialValue: links.value[i].name,
 | 
				
			||||||
                                decoration: InputDecoration(
 | 
					                                decoration: InputDecoration(
 | 
				
			||||||
                                  labelText: 'linkKey'.tr(),
 | 
					                                  labelText: 'linkKey'.tr(),
 | 
				
			||||||
                                  isDense: true,
 | 
					                                  isDense: true,
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                                onChanged: (value) {
 | 
					                                onChanged: (value) {
 | 
				
			||||||
                                  links.value[i]['key'] = value;
 | 
					                                  links.value[i] = links.value[i].copyWith(
 | 
				
			||||||
 | 
					                                    name: value,
 | 
				
			||||||
 | 
					                                  );
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                                onTapOutside:
 | 
					                                onTapOutside:
 | 
				
			||||||
                                    (_) =>
 | 
					                                    (_) =>
 | 
				
			||||||
@@ -592,13 +591,15 @@ class UpdateProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                            const Gap(8),
 | 
					                            const Gap(8),
 | 
				
			||||||
                            Expanded(
 | 
					                            Expanded(
 | 
				
			||||||
                              child: TextFormField(
 | 
					                              child: TextFormField(
 | 
				
			||||||
                                initialValue: links.value[i]['value'],
 | 
					                                initialValue: links.value[i].url,
 | 
				
			||||||
                                decoration: InputDecoration(
 | 
					                                decoration: InputDecoration(
 | 
				
			||||||
                                  labelText: 'linkValue'.tr(),
 | 
					                                  labelText: 'linkValue'.tr(),
 | 
				
			||||||
                                  isDense: true,
 | 
					                                  isDense: true,
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                                onChanged: (value) {
 | 
					                                onChanged: (value) {
 | 
				
			||||||
                                  links.value[i]['value'] = value;
 | 
					                                  links.value[i] = links.value[i].copyWith(
 | 
				
			||||||
 | 
					                                    url: value,
 | 
				
			||||||
 | 
					                                  );
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                                onTapOutside:
 | 
					                                onTapOutside:
 | 
				
			||||||
                                    (_) =>
 | 
					                                    (_) =>
 | 
				
			||||||
@@ -620,7 +621,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                        child: FilledButton.icon(
 | 
					                        child: FilledButton.icon(
 | 
				
			||||||
                          onPressed: () {
 | 
					                          onPressed: () {
 | 
				
			||||||
                            links.value = List.from(links.value)
 | 
					                            links.value = List.from(links.value)
 | 
				
			||||||
                              ..add({'key': '', 'value': ''});
 | 
					                              ..add(ProfileLink(name: '', url: ''));
 | 
				
			||||||
                          },
 | 
					                          },
 | 
				
			||||||
                          label: Text('addLink').tr(),
 | 
					                          label: Text('addLink').tr(),
 | 
				
			||||||
                          icon: const Icon(Symbols.add),
 | 
					                          icon: const Icon(Symbols.add),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import 'package:dio/dio.dart';
 | 
					import 'package:dio/dio.dart';
 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:go_router/go_router.dart';
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
import 'package:gap/gap.dart';
 | 
					import 'package:gap/gap.dart';
 | 
				
			||||||
@@ -13,6 +14,7 @@ import 'package:island/pods/network.dart';
 | 
				
			|||||||
import 'package:island/pods/userinfo.dart';
 | 
					import 'package:island/pods/userinfo.dart';
 | 
				
			||||||
import 'package:island/services/color.dart';
 | 
					import 'package:island/services/color.dart';
 | 
				
			||||||
import 'package:island/services/responsive.dart';
 | 
					import 'package:island/services/responsive.dart';
 | 
				
			||||||
 | 
					import 'package:island/services/text.dart';
 | 
				
			||||||
import 'package:island/services/time.dart';
 | 
					import 'package:island/services/time.dart';
 | 
				
			||||||
import 'package:island/services/timezone/native.dart';
 | 
					import 'package:island/services/timezone/native.dart';
 | 
				
			||||||
import 'package:island/widgets/account/account_name.dart';
 | 
					import 'package:island/widgets/account/account_name.dart';
 | 
				
			||||||
@@ -30,6 +32,7 @@ import 'package:palette_generator/palette_generator.dart';
 | 
				
			|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
import 'package:share_plus/share_plus.dart';
 | 
					import 'package:share_plus/share_plus.dart';
 | 
				
			||||||
import 'package:styled_widget/styled_widget.dart';
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
 | 
					import 'package:url_launcher/url_launcher_string.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
part 'profile.g.dart';
 | 
					part 'profile.g.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -194,6 +197,15 @@ class AccountProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    List<Widget> buildSubcolumn(SnAccount data) {
 | 
					    List<Widget> buildSubcolumn(SnAccount data) {
 | 
				
			||||||
      return [
 | 
					      return [
 | 
				
			||||||
 | 
					        Row(
 | 
				
			||||||
 | 
					          spacing: 6,
 | 
				
			||||||
 | 
					          children: [
 | 
				
			||||||
 | 
					            const Icon(Symbols.join, size: 17, fill: 1),
 | 
				
			||||||
 | 
					            Text(
 | 
				
			||||||
 | 
					              'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
        if (data.profile.birthday != null)
 | 
					        if (data.profile.birthday != null)
 | 
				
			||||||
          Row(
 | 
					          Row(
 | 
				
			||||||
            spacing: 6,
 | 
					            spacing: 6,
 | 
				
			||||||
@@ -320,7 +332,7 @@ class AccountProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
              spacing: 2,
 | 
					              spacing: 2,
 | 
				
			||||||
              children: buildSubcolumn(data),
 | 
					              children: buildSubcolumn(data),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          if (data.profile.timeZone.isNotEmpty)
 | 
					          if (data.profile.timeZone.isNotEmpty && !kIsWeb)
 | 
				
			||||||
            Column(
 | 
					            Column(
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
					              crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
@@ -350,6 +362,32 @@ class AccountProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
      ).padding(horizontal: 24, vertical: 16),
 | 
					      ).padding(horizontal: 24, vertical: 16),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Widget accountProfileLinks(SnAccount data) => Card(
 | 
				
			||||||
 | 
					      child: Column(
 | 
				
			||||||
 | 
					        crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
 | 
				
			||||||
 | 
					          for (final link in data.profile.links)
 | 
				
			||||||
 | 
					            ListTile(
 | 
				
			||||||
 | 
					              title: Text(link.name.capitalizeEachWord()),
 | 
				
			||||||
 | 
					              subtitle: Text(link.url),
 | 
				
			||||||
 | 
					              contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					              trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					              shape: RoundedRectangleBorder(
 | 
				
			||||||
 | 
					                borderRadius: const BorderRadius.all(Radius.circular(8)),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              onTap: () {
 | 
				
			||||||
 | 
					                if (!link.url.startsWith('http') && !link.url.contains('://')) {
 | 
				
			||||||
 | 
					                  launchUrlString('https://${link.url}');
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                  launchUrlString(link.url);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Widget accountAction(SnAccount data) => Card(
 | 
					    Widget accountAction(SnAccount data) => Card(
 | 
				
			||||||
      child: Column(
 | 
					      child: Column(
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
@@ -452,7 +490,7 @@ class AccountProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
            ],
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ).padding(horizontal: 16, vertical: 8),
 | 
					      ).padding(horizontal: 16, vertical: 12),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return account.when(
 | 
					    return account.when(
 | 
				
			||||||
@@ -537,6 +575,10 @@ class AccountProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                              SliverToBoxAdapter(
 | 
					                              SliverToBoxAdapter(
 | 
				
			||||||
                                child: accountProfileBio(data).padding(top: 4),
 | 
					                                child: accountProfileBio(data).padding(top: 4),
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
 | 
					                              if (data.profile.links.isNotEmpty)
 | 
				
			||||||
 | 
					                                SliverToBoxAdapter(
 | 
				
			||||||
 | 
					                                  child: accountProfileLinks(data),
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
                              SliverToBoxAdapter(
 | 
					                              SliverToBoxAdapter(
 | 
				
			||||||
                                child: accountProfileDetail(data),
 | 
					                                child: accountProfileDetail(data),
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
@@ -633,6 +675,12 @@ class AccountProfileScreen extends HookConsumerWidget {
 | 
				
			|||||||
                        SliverToBoxAdapter(
 | 
					                        SliverToBoxAdapter(
 | 
				
			||||||
                          child: accountProfileBio(data).padding(horizontal: 4),
 | 
					                          child: accountProfileBio(data).padding(horizontal: 4),
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
 | 
					                        if (data.profile.links.isNotEmpty)
 | 
				
			||||||
 | 
					                          SliverToBoxAdapter(
 | 
				
			||||||
 | 
					                            child: accountProfileLinks(
 | 
				
			||||||
 | 
					                              data,
 | 
				
			||||||
 | 
					                            ).padding(horizontal: 4),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
                        SliverToBoxAdapter(
 | 
					                        SliverToBoxAdapter(
 | 
				
			||||||
                          child: accountProfileDetail(
 | 
					                          child: accountProfileDetail(
 | 
				
			||||||
                            data,
 | 
					                            data,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:island/models/poll.dart';
 | 
					import 'package:island/models/poll.dart';
 | 
				
			||||||
import 'package:island/pods/network.dart';
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
import 'package:island/widgets/poll/poll_feedback.dart';
 | 
					import 'package:island/widgets/poll/poll_feedback.dart';
 | 
				
			||||||
import 'package:material_symbols_icons/symbols.dart';
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
					import 'package:riverpod_annotation/riverpod_annotation.dart';
 | 
				
			||||||
@@ -70,7 +71,7 @@ class CreatorPollListScreen extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
    return Scaffold(
 | 
					    return AppScaffold(
 | 
				
			||||||
      appBar: AppBar(title: const Text('Polls')),
 | 
					      appBar: AppBar(title: const Text('Polls')),
 | 
				
			||||||
      floatingActionButton: FloatingActionButton(
 | 
					      floatingActionButton: FloatingActionButton(
 | 
				
			||||||
        onPressed: () => _createPoll(context),
 | 
					        onPressed: () => _createPoll(context),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
 | 
				
			|||||||
      try {
 | 
					      try {
 | 
				
			||||||
        showLoadingModal(context);
 | 
					        showLoadingModal(context);
 | 
				
			||||||
        final apiClient = ref.watch(apiClientProvider);
 | 
					        final apiClient = ref.watch(apiClientProvider);
 | 
				
			||||||
        await apiClient.delete('/stickers/$id/content/${sticker.id}');
 | 
					        await apiClient.delete('/sphere/stickers/$id/content/${sticker.id}');
 | 
				
			||||||
        ref.invalidate(stickerPackContentProvider(id));
 | 
					        ref.invalidate(stickerPackContentProvider(id));
 | 
				
			||||||
      } catch (err) {
 | 
					      } catch (err) {
 | 
				
			||||||
        showErrorAlert(err);
 | 
					        showErrorAlert(err);
 | 
				
			||||||
@@ -297,7 +297,7 @@ class _StickerPackActionMenu extends HookConsumerWidget {
 | 
				
			|||||||
                ).then((confirm) {
 | 
					                ).then((confirm) {
 | 
				
			||||||
                  if (confirm) {
 | 
					                  if (confirm) {
 | 
				
			||||||
                    final client = ref.watch(apiClientProvider);
 | 
					                    final client = ref.watch(apiClientProvider);
 | 
				
			||||||
                    client.delete('/stickers/$packId');
 | 
					                    client.delete('/sphere/stickers/$packId');
 | 
				
			||||||
                    ref.invalidate(stickerPacksNotifierProvider);
 | 
					                    ref.invalidate(stickerPacksNotifierProvider);
 | 
				
			||||||
                    if (context.mounted) context.pop(true);
 | 
					                    if (context.mounted) context.pop(true);
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
@@ -325,7 +325,7 @@ Future<SnSticker?> stickerPackSticker(
 | 
				
			|||||||
  if (query == null) return null;
 | 
					  if (query == null) return null;
 | 
				
			||||||
  final apiClient = ref.watch(apiClientProvider);
 | 
					  final apiClient = ref.watch(apiClientProvider);
 | 
				
			||||||
  final resp = await apiClient.get(
 | 
					  final resp = await apiClient.get(
 | 
				
			||||||
    '/stickers/${query.packId}/content/${query.id}',
 | 
					    '/sphere/stickers/${query.packId}/content/${query.id}',
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  if (resp.data == null) return null;
 | 
					  if (resp.data == null) return null;
 | 
				
			||||||
  return SnSticker.fromJson(resp.data);
 | 
					  return SnSticker.fromJson(resp.data);
 | 
				
			||||||
@@ -379,8 +379,8 @@ class EditStickersScreen extends HookConsumerWidget {
 | 
				
			|||||||
      try {
 | 
					      try {
 | 
				
			||||||
        final resp = await apiClient.request(
 | 
					        final resp = await apiClient.request(
 | 
				
			||||||
          id == null
 | 
					          id == null
 | 
				
			||||||
              ? '/stickers/$packId/content'
 | 
					              ? '/sphere/stickers/$packId/content'
 | 
				
			||||||
              : '/stickers/$packId/content/$id',
 | 
					              : '/sphere/stickers/$packId/content/$id',
 | 
				
			||||||
          data: {'slug': slugController.text, 'image_id': imageController.text},
 | 
					          data: {'slug': slugController.text, 'image_id': imageController.text},
 | 
				
			||||||
          options: Options(method: id == null ? 'POST' : 'PATCH'),
 | 
					          options: Options(method: id == null ? 'POST' : 'PATCH'),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -151,7 +151,7 @@ class _StickerPackContentProviderElement
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
String _$stickerPackStickerHash() =>
 | 
					String _$stickerPackStickerHash() =>
 | 
				
			||||||
    r'36f524c047e632236d5597aaaa8678ed86599602';
 | 
					    r'5c553666b3a63530bdebae4b7cd52f303c5ab3a0';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// See also [stickerPackSticker].
 | 
					/// See also [stickerPackSticker].
 | 
				
			||||||
@ProviderFor(stickerPackSticker)
 | 
					@ProviderFor(stickerPackSticker)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,10 +114,11 @@ class WebFeedEditScreen extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return feedAsync.when(
 | 
					    return feedAsync.when(
 | 
				
			||||||
      loading:
 | 
					      loading:
 | 
				
			||||||
          () =>
 | 
					          () => const AppScaffold(
 | 
				
			||||||
              const Scaffold(body: Center(child: CircularProgressIndicator())),
 | 
					            body: Center(child: CircularProgressIndicator()),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
      error:
 | 
					      error:
 | 
				
			||||||
          (error, stack) => Scaffold(
 | 
					          (error, stack) => AppScaffold(
 | 
				
			||||||
            appBar: AppBar(title: const Text('Error')),
 | 
					            appBar: AppBar(title: const Text('Error')),
 | 
				
			||||||
            body: Center(child: Text('Error: $error')),
 | 
					            body: Center(child: Text('Error: $error')),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,12 +30,12 @@ Future<DeveloperStats?> developerStats(Ref ref, String? uname) async {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@riverpod
 | 
					@riverpod
 | 
				
			||||||
Future<List<SnPublisher>> developers(Ref ref) async {
 | 
					Future<List<SnDeveloper>> developers(Ref ref) async {
 | 
				
			||||||
  final client = ref.watch(apiClientProvider);
 | 
					  final client = ref.watch(apiClientProvider);
 | 
				
			||||||
  final resp = await client.get('/develop/developers');
 | 
					  final resp = await client.get('/develop/developers');
 | 
				
			||||||
  return resp.data
 | 
					  return resp.data
 | 
				
			||||||
      .map((e) => SnPublisher.fromJson(e))
 | 
					      .map((e) => SnDeveloper.fromJson(e))
 | 
				
			||||||
      .cast<SnPublisher>()
 | 
					      .cast<SnDeveloper>()
 | 
				
			||||||
      .toList();
 | 
					      .toList();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,25 +74,25 @@ class DeveloperHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final developers = ref.watch(developersProvider);
 | 
					    final developers = ref.watch(developersProvider);
 | 
				
			||||||
    final currentDeveloper = useState<SnPublisher?>(
 | 
					    final currentDeveloper = useState<SnDeveloper?>(
 | 
				
			||||||
      developers.value?.firstOrNull,
 | 
					      developers.value?.firstOrNull,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final List<DropdownMenuItem<SnPublisher>> developersMenu = developers.when(
 | 
					    final List<DropdownMenuItem<SnDeveloper>> developersMenu = developers.when(
 | 
				
			||||||
      data:
 | 
					      data:
 | 
				
			||||||
          (data) =>
 | 
					          (data) =>
 | 
				
			||||||
              data
 | 
					              data
 | 
				
			||||||
                  .map(
 | 
					                  .map(
 | 
				
			||||||
                    (item) => DropdownMenuItem<SnPublisher>(
 | 
					                    (item) => DropdownMenuItem<SnDeveloper>(
 | 
				
			||||||
                      value: item,
 | 
					                      value: item,
 | 
				
			||||||
                      child: ListTile(
 | 
					                      child: ListTile(
 | 
				
			||||||
                        minTileHeight: 48,
 | 
					                        minTileHeight: 48,
 | 
				
			||||||
                        leading: ProfilePictureWidget(
 | 
					                        leading: ProfilePictureWidget(
 | 
				
			||||||
                          radius: 16,
 | 
					                          radius: 16,
 | 
				
			||||||
                          fileId: item.picture?.id,
 | 
					                          fileId: item.publisher?.picture?.id,
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                        title: Text(item.nick),
 | 
					                        title: Text(item.publisher!.nick),
 | 
				
			||||||
                        subtitle: Text('@${item.name}'),
 | 
					                        subtitle: Text('@${item.publisher!.name}'),
 | 
				
			||||||
                        trailing:
 | 
					                        trailing:
 | 
				
			||||||
                            currentDeveloper.value?.id == item.id
 | 
					                            currentDeveloper.value?.id == item.id
 | 
				
			||||||
                                ? const Icon(Icons.check)
 | 
					                                ? const Icon(Icons.check)
 | 
				
			||||||
@@ -107,7 +107,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final developerStats = ref.watch(
 | 
					    final developerStats = ref.watch(
 | 
				
			||||||
      developerStatsProvider(currentDeveloper.value?.name),
 | 
					      developerStatsProvider(currentDeveloper.value?.publisher?.name),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return AppScaffold(
 | 
					    return AppScaffold(
 | 
				
			||||||
@@ -117,7 +117,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
        title: Text('developerHub').tr(),
 | 
					        title: Text('developerHub').tr(),
 | 
				
			||||||
        actions: [
 | 
					        actions: [
 | 
				
			||||||
          DropdownButtonHideUnderline(
 | 
					          DropdownButtonHideUnderline(
 | 
				
			||||||
            child: DropdownButton2<SnPublisher>(
 | 
					            child: DropdownButton2<SnDeveloper>(
 | 
				
			||||||
              alignment: Alignment.centerRight,
 | 
					              alignment: Alignment.centerRight,
 | 
				
			||||||
              value: currentDeveloper.value,
 | 
					              value: currentDeveloper.value,
 | 
				
			||||||
              hint: CircleAvatar(
 | 
					              hint: CircleAvatar(
 | 
				
			||||||
@@ -139,7 +139,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
                  ...developersMenu.map(
 | 
					                  ...developersMenu.map(
 | 
				
			||||||
                    (e) => ProfilePictureWidget(
 | 
					                    (e) => ProfilePictureWidget(
 | 
				
			||||||
                      radius: 16,
 | 
					                      radius: 16,
 | 
				
			||||||
                      fileId: e.value?.picture?.id,
 | 
					                      fileId: e.value?.publisher?.picture?.id,
 | 
				
			||||||
                    ).center().padding(right: 8),
 | 
					                    ).center().padding(right: 8),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ];
 | 
					                ];
 | 
				
			||||||
@@ -193,10 +193,12 @@ class DeveloperHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
                          ...(developers.value?.map(
 | 
					                          ...(developers.value?.map(
 | 
				
			||||||
                                (developer) => ListTile(
 | 
					                                (developer) => ListTile(
 | 
				
			||||||
                                  leading: ProfilePictureWidget(
 | 
					                                  leading: ProfilePictureWidget(
 | 
				
			||||||
                                    file: developer.picture,
 | 
					                                    file: developer.publisher?.picture,
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                  title: Text(developer.publisher!.nick),
 | 
				
			||||||
 | 
					                                  subtitle: Text(
 | 
				
			||||||
 | 
					                                    '@${developer.publisher!.name}',
 | 
				
			||||||
                                  ),
 | 
					                                  ),
 | 
				
			||||||
                                  title: Text(developer.nick),
 | 
					 | 
				
			||||||
                                  subtitle: Text('@${developer.name}'),
 | 
					 | 
				
			||||||
                                  onTap: () {
 | 
					                                  onTap: () {
 | 
				
			||||||
                                    currentDeveloper.value = developer;
 | 
					                                    currentDeveloper.value = developer;
 | 
				
			||||||
                                  },
 | 
					                                  },
 | 
				
			||||||
@@ -243,7 +245,8 @@ class DeveloperHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
                              context.pushNamed(
 | 
					                              context.pushNamed(
 | 
				
			||||||
                                'developerApps',
 | 
					                                'developerApps',
 | 
				
			||||||
                                pathParameters: {
 | 
					                                pathParameters: {
 | 
				
			||||||
                                  'name': currentDeveloper.value!.name,
 | 
					                                  'name':
 | 
				
			||||||
 | 
					                                      currentDeveloper.value!.publisher!.name,
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                              );
 | 
					                              );
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
@@ -257,7 +260,9 @@ class DeveloperHubScreen extends HookConsumerWidget {
 | 
				
			|||||||
              error: err,
 | 
					              error: err,
 | 
				
			||||||
              onRetry: () {
 | 
					              onRetry: () {
 | 
				
			||||||
                ref.invalidate(
 | 
					                ref.invalidate(
 | 
				
			||||||
                  developerStatsProvider(currentDeveloper.value?.name),
 | 
					                  developerStatsProvider(
 | 
				
			||||||
 | 
					                    currentDeveloper.value?.publisher!.name,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -354,7 +359,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
 | 
				
			|||||||
                    ? Center(
 | 
					                    ? Center(
 | 
				
			||||||
                      child:
 | 
					                      child:
 | 
				
			||||||
                          Text(
 | 
					                          Text(
 | 
				
			||||||
                            'noPublishersToEnroll',
 | 
					                            'noDevelopersToEnroll',
 | 
				
			||||||
                            textAlign: TextAlign.center,
 | 
					                            textAlign: TextAlign.center,
 | 
				
			||||||
                          ).tr(),
 | 
					                          ).tr(),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -149,12 +149,12 @@ class _DeveloperStatsProviderElement
 | 
				
			|||||||
  String? get uname => (origin as DeveloperStatsProvider).uname;
 | 
					  String? get uname => (origin as DeveloperStatsProvider).uname;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
String _$developersHash() => r'04f25db31f511f651a5add128d56631236ed0b39';
 | 
					String _$developersHash() => r'252341098617ac398ce133994453f318dd3edbd2';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// See also [developers].
 | 
					/// See also [developers].
 | 
				
			||||||
@ProviderFor(developers)
 | 
					@ProviderFor(developers)
 | 
				
			||||||
final developersProvider =
 | 
					final developersProvider =
 | 
				
			||||||
    AutoDisposeFutureProvider<List<SnPublisher>>.internal(
 | 
					    AutoDisposeFutureProvider<List<SnDeveloper>>.internal(
 | 
				
			||||||
      developers,
 | 
					      developers,
 | 
				
			||||||
      name: r'developersProvider',
 | 
					      name: r'developersProvider',
 | 
				
			||||||
      debugGetCreateSourceHash:
 | 
					      debugGetCreateSourceHash:
 | 
				
			||||||
@@ -167,6 +167,6 @@ final developersProvider =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
					@Deprecated('Will be removed in 3.0. Use Ref instead')
 | 
				
			||||||
// ignore: unused_element
 | 
					// ignore: unused_element
 | 
				
			||||||
typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnPublisher>>;
 | 
					typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnDeveloper>>;
 | 
				
			||||||
// ignore_for_file: type=lint
 | 
					// ignore_for_file: type=lint
 | 
				
			||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
					// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import 'package:gap/gap.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/models/poll.dart';
 | 
					import 'package:island/models/poll.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/app_scaffold.dart';
 | 
				
			||||||
import 'package:uuid/uuid.dart';
 | 
					import 'package:uuid/uuid.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PollEditorState {
 | 
					class PollEditorState {
 | 
				
			||||||
@@ -413,7 +414,7 @@ class PollEditorScreen extends ConsumerWidget {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return AppScaffold(
 | 
				
			||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
        title: Text(model.id == null ? 'Create Poll' : 'Edit Poll'),
 | 
					        title: Text(model.id == null ? 'Create Poll' : 'Edit Poll'),
 | 
				
			||||||
        actions: [
 | 
					        actions: [
 | 
				
			||||||
@@ -428,175 +429,175 @@ class PollEditorScreen extends ConsumerWidget {
 | 
				
			|||||||
          const Gap(8),
 | 
					          const Gap(8),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      body: SafeArea(
 | 
					      body: Column(
 | 
				
			||||||
        child: Form(
 | 
					        children: [
 | 
				
			||||||
          key: ValueKey(model.id),
 | 
					          Expanded(
 | 
				
			||||||
          child: ListView(
 | 
					            child: Form(
 | 
				
			||||||
            padding: const EdgeInsets.all(16),
 | 
					              key: ValueKey(model.id),
 | 
				
			||||||
            children: [
 | 
					              child: ListView(
 | 
				
			||||||
              TextFormField(
 | 
					                padding: const EdgeInsets.all(16),
 | 
				
			||||||
                initialValue: model.title ?? '',
 | 
					 | 
				
			||||||
                decoration: const InputDecoration(
 | 
					 | 
				
			||||||
                  labelText: 'Title',
 | 
					 | 
				
			||||||
                  border: OutlineInputBorder(
 | 
					 | 
				
			||||||
                    borderRadius: BorderRadius.all(Radius.circular(16)),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                textInputAction: TextInputAction.next,
 | 
					 | 
				
			||||||
                maxLength: 256,
 | 
					 | 
				
			||||||
                onChanged: notifier.setTitle,
 | 
					 | 
				
			||||||
                onTapOutside:
 | 
					 | 
				
			||||||
                    (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
					 | 
				
			||||||
                validator: (v) {
 | 
					 | 
				
			||||||
                  if (v == null || v.trim().isEmpty) {
 | 
					 | 
				
			||||||
                    return 'Title is required';
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                  return null;
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              const Gap(12),
 | 
					 | 
				
			||||||
              TextFormField(
 | 
					 | 
				
			||||||
                initialValue: model.description ?? '',
 | 
					 | 
				
			||||||
                decoration: const InputDecoration(
 | 
					 | 
				
			||||||
                  labelText: 'Description',
 | 
					 | 
				
			||||||
                  alignLabelWithHint: true,
 | 
					 | 
				
			||||||
                  border: OutlineInputBorder(
 | 
					 | 
				
			||||||
                    borderRadius: BorderRadius.all(Radius.circular(16)),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                maxLines: 3,
 | 
					 | 
				
			||||||
                maxLength: 4096,
 | 
					 | 
				
			||||||
                onChanged: notifier.setDescription,
 | 
					 | 
				
			||||||
                onTapOutside:
 | 
					 | 
				
			||||||
                    (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              const Gap(12),
 | 
					 | 
				
			||||||
              _EndDatePicker(
 | 
					 | 
				
			||||||
                value: model.endedAt,
 | 
					 | 
				
			||||||
                onChanged: notifier.setEndedAt,
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              const Gap(24),
 | 
					 | 
				
			||||||
              Row(
 | 
					 | 
				
			||||||
                children: [
 | 
					                children: [
 | 
				
			||||||
                  Text(
 | 
					                  TextFormField(
 | 
				
			||||||
                    'Questions',
 | 
					                    initialValue: model.title ?? '',
 | 
				
			||||||
                    style: Theme.of(context).textTheme.titleLarge,
 | 
					                    decoration: const InputDecoration(
 | 
				
			||||||
                  ),
 | 
					                      labelText: 'Title',
 | 
				
			||||||
                  const Spacer(),
 | 
					                      border: OutlineInputBorder(
 | 
				
			||||||
                  MenuAnchor(
 | 
					                        borderRadius: BorderRadius.all(Radius.circular(16)),
 | 
				
			||||||
                    builder: (context, controller, child) {
 | 
					                      ),
 | 
				
			||||||
                      return FilledButton.icon(
 | 
					                    ),
 | 
				
			||||||
                        onPressed: () {
 | 
					                    textInputAction: TextInputAction.next,
 | 
				
			||||||
                          controller.isOpen
 | 
					                    maxLength: 256,
 | 
				
			||||||
                              ? controller.close()
 | 
					                    onChanged: notifier.setTitle,
 | 
				
			||||||
                              : controller.open();
 | 
					                    onTapOutside:
 | 
				
			||||||
                        },
 | 
					                        (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
				
			||||||
                        icon: const Icon(Icons.add),
 | 
					                    validator: (v) {
 | 
				
			||||||
                        label: const Text('Add question'),
 | 
					                      if (v == null || v.trim().isEmpty) {
 | 
				
			||||||
                      );
 | 
					                        return 'Title is required';
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                      return null;
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    menuChildren:
 | 
					 | 
				
			||||||
                        SnPollQuestionType.values
 | 
					 | 
				
			||||||
                            .map(
 | 
					 | 
				
			||||||
                              (t) => MenuItemButton(
 | 
					 | 
				
			||||||
                                leadingIcon: Icon(_iconForType(t)),
 | 
					 | 
				
			||||||
                                onPressed: () => notifier.addQuestion(t),
 | 
					 | 
				
			||||||
                                child: Text(_labelForType(t)),
 | 
					 | 
				
			||||||
                              ),
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                            .toList(),
 | 
					 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					                  const Gap(12),
 | 
				
			||||||
 | 
					                  TextFormField(
 | 
				
			||||||
 | 
					                    initialValue: model.description ?? '',
 | 
				
			||||||
 | 
					                    decoration: const InputDecoration(
 | 
				
			||||||
 | 
					                      labelText: 'Description',
 | 
				
			||||||
 | 
					                      alignLabelWithHint: true,
 | 
				
			||||||
 | 
					                      border: OutlineInputBorder(
 | 
				
			||||||
 | 
					                        borderRadius: BorderRadius.all(Radius.circular(16)),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    maxLines: 3,
 | 
				
			||||||
 | 
					                    maxLength: 4096,
 | 
				
			||||||
 | 
					                    onChanged: notifier.setDescription,
 | 
				
			||||||
 | 
					                    onTapOutside:
 | 
				
			||||||
 | 
					                        (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  const Gap(12),
 | 
				
			||||||
 | 
					                  _EndDatePicker(
 | 
				
			||||||
 | 
					                    value: model.endedAt,
 | 
				
			||||||
 | 
					                    onChanged: notifier.setEndedAt,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  const Gap(24),
 | 
				
			||||||
 | 
					                  Row(
 | 
				
			||||||
 | 
					                    children: [
 | 
				
			||||||
 | 
					                      Text(
 | 
				
			||||||
 | 
					                        'Questions',
 | 
				
			||||||
 | 
					                        style: Theme.of(context).textTheme.titleLarge,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      const Spacer(),
 | 
				
			||||||
 | 
					                      MenuAnchor(
 | 
				
			||||||
 | 
					                        builder: (context, controller, child) {
 | 
				
			||||||
 | 
					                          return FilledButton.icon(
 | 
				
			||||||
 | 
					                            onPressed: () {
 | 
				
			||||||
 | 
					                              controller.isOpen
 | 
				
			||||||
 | 
					                                  ? controller.close()
 | 
				
			||||||
 | 
					                                  : controller.open();
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            icon: const Icon(Icons.add),
 | 
				
			||||||
 | 
					                            label: const Text('Add question'),
 | 
				
			||||||
 | 
					                          );
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        menuChildren:
 | 
				
			||||||
 | 
					                            SnPollQuestionType.values
 | 
				
			||||||
 | 
					                                .map(
 | 
				
			||||||
 | 
					                                  (t) => MenuItemButton(
 | 
				
			||||||
 | 
					                                    leadingIcon: Icon(_iconForType(t)),
 | 
				
			||||||
 | 
					                                    onPressed: () => notifier.addQuestion(t),
 | 
				
			||||||
 | 
					                                    child: Text(_labelForType(t)),
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                                .toList(),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  const Gap(8),
 | 
				
			||||||
 | 
					                  if (model.questions.isEmpty)
 | 
				
			||||||
 | 
					                    _EmptyState(
 | 
				
			||||||
 | 
					                      title: 'No questions yet',
 | 
				
			||||||
 | 
					                      subtitle:
 | 
				
			||||||
 | 
					                          'Use "Add question" to start building your poll.',
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                  else
 | 
				
			||||||
 | 
					                    ReorderableListView.builder(
 | 
				
			||||||
 | 
					                      shrinkWrap: true,
 | 
				
			||||||
 | 
					                      physics: const NeverScrollableScrollPhysics(),
 | 
				
			||||||
 | 
					                      itemCount: model.questions.length,
 | 
				
			||||||
 | 
					                      onReorder: (oldIndex, newIndex) {
 | 
				
			||||||
 | 
					                        // Convert to stepwise moves using provided functions
 | 
				
			||||||
 | 
					                        if (newIndex > oldIndex) newIndex -= 1;
 | 
				
			||||||
 | 
					                        final steps = newIndex - oldIndex;
 | 
				
			||||||
 | 
					                        if (steps == 0) return;
 | 
				
			||||||
 | 
					                        if (steps > 0) {
 | 
				
			||||||
 | 
					                          for (int i = 0; i < steps; i++) {
 | 
				
			||||||
 | 
					                            notifier.moveQuestionDown(oldIndex + i);
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                          for (int i = 0; i > steps; i--) {
 | 
				
			||||||
 | 
					                            notifier.moveQuestionUp(oldIndex + i);
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                      buildDefaultDragHandles: false,
 | 
				
			||||||
 | 
					                      itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                        final q = model.questions[index];
 | 
				
			||||||
 | 
					                        return Card(
 | 
				
			||||||
 | 
					                          key: ValueKey('q_$index'),
 | 
				
			||||||
 | 
					                          margin: const EdgeInsets.symmetric(vertical: 8),
 | 
				
			||||||
 | 
					                          clipBehavior: Clip.antiAlias,
 | 
				
			||||||
 | 
					                          child: Column(
 | 
				
			||||||
 | 
					                            children: [
 | 
				
			||||||
 | 
					                              _QuestionHeader(
 | 
				
			||||||
 | 
					                                index: index,
 | 
				
			||||||
 | 
					                                question: q,
 | 
				
			||||||
 | 
					                                onMoveUp:
 | 
				
			||||||
 | 
					                                    index > 0
 | 
				
			||||||
 | 
					                                        ? () => notifier.moveQuestionUp(index)
 | 
				
			||||||
 | 
					                                        : null,
 | 
				
			||||||
 | 
					                                onMoveDown:
 | 
				
			||||||
 | 
					                                    index < model.questions.length - 1
 | 
				
			||||||
 | 
					                                        ? () => notifier.moveQuestionDown(index)
 | 
				
			||||||
 | 
					                                        : null,
 | 
				
			||||||
 | 
					                                onDelete: () => notifier.removeQuestion(index),
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                              const Divider(height: 1),
 | 
				
			||||||
 | 
					                              Padding(
 | 
				
			||||||
 | 
					                                padding: const EdgeInsets.all(16),
 | 
				
			||||||
 | 
					                                child: _QuestionEditor(
 | 
				
			||||||
 | 
					                                  index: index,
 | 
				
			||||||
 | 
					                                  question: q,
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            ],
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  const Gap(96),
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              const Gap(8),
 | 
					            ),
 | 
				
			||||||
              if (model.questions.isEmpty)
 | 
					          ),
 | 
				
			||||||
                _EmptyState(
 | 
					          Row(
 | 
				
			||||||
                  title: 'No questions yet',
 | 
					            children: [
 | 
				
			||||||
                  subtitle: 'Use "Add question" to start building your poll.',
 | 
					              OutlinedButton.icon(
 | 
				
			||||||
                )
 | 
					                onPressed: () {
 | 
				
			||||||
              else
 | 
					                  Navigator.of(context).maybePop();
 | 
				
			||||||
                ReorderableListView.builder(
 | 
					                },
 | 
				
			||||||
                  shrinkWrap: true,
 | 
					                icon: const Icon(Icons.close),
 | 
				
			||||||
                  physics: const NeverScrollableScrollPhysics(),
 | 
					                label: const Text('Cancel'),
 | 
				
			||||||
                  itemCount: model.questions.length,
 | 
					              ),
 | 
				
			||||||
                  onReorder: (oldIndex, newIndex) {
 | 
					              const Spacer(),
 | 
				
			||||||
                    // Convert to stepwise moves using provided functions
 | 
					              FilledButton.icon(
 | 
				
			||||||
                    if (newIndex > oldIndex) newIndex -= 1;
 | 
					                onPressed: () {
 | 
				
			||||||
                    final steps = newIndex - oldIndex;
 | 
					                  _submitPoll(context, ref);
 | 
				
			||||||
                    if (steps == 0) return;
 | 
					                },
 | 
				
			||||||
                    if (steps > 0) {
 | 
					                icon: const Icon(Icons.cloud_upload_outlined),
 | 
				
			||||||
                      for (int i = 0; i < steps; i++) {
 | 
					                label: Text(model.id == null ? 'Create' : 'Update'),
 | 
				
			||||||
                        notifier.moveQuestionDown(oldIndex + i);
 | 
					              ),
 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                      for (int i = 0; i > steps; i--) {
 | 
					 | 
				
			||||||
                        notifier.moveQuestionUp(oldIndex + i);
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  },
 | 
					 | 
				
			||||||
                  buildDefaultDragHandles: false,
 | 
					 | 
				
			||||||
                  itemBuilder: (context, index) {
 | 
					 | 
				
			||||||
                    final q = model.questions[index];
 | 
					 | 
				
			||||||
                    return Card(
 | 
					 | 
				
			||||||
                      key: ValueKey('q_$index'),
 | 
					 | 
				
			||||||
                      margin: const EdgeInsets.symmetric(vertical: 8),
 | 
					 | 
				
			||||||
                      clipBehavior: Clip.antiAlias,
 | 
					 | 
				
			||||||
                      child: Column(
 | 
					 | 
				
			||||||
                        children: [
 | 
					 | 
				
			||||||
                          _QuestionHeader(
 | 
					 | 
				
			||||||
                            index: index,
 | 
					 | 
				
			||||||
                            question: q,
 | 
					 | 
				
			||||||
                            onMoveUp:
 | 
					 | 
				
			||||||
                                index > 0
 | 
					 | 
				
			||||||
                                    ? () => notifier.moveQuestionUp(index)
 | 
					 | 
				
			||||||
                                    : null,
 | 
					 | 
				
			||||||
                            onMoveDown:
 | 
					 | 
				
			||||||
                                index < model.questions.length - 1
 | 
					 | 
				
			||||||
                                    ? () => notifier.moveQuestionDown(index)
 | 
					 | 
				
			||||||
                                    : null,
 | 
					 | 
				
			||||||
                            onDelete: () => notifier.removeQuestion(index),
 | 
					 | 
				
			||||||
                          ),
 | 
					 | 
				
			||||||
                          const Divider(height: 1),
 | 
					 | 
				
			||||||
                          Padding(
 | 
					 | 
				
			||||||
                            padding: const EdgeInsets.all(16),
 | 
					 | 
				
			||||||
                            child: _QuestionEditor(index: index, question: q),
 | 
					 | 
				
			||||||
                          ),
 | 
					 | 
				
			||||||
                        ],
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                  },
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              const Gap(96),
 | 
					 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
      bottomNavigationBar: Padding(
 | 
					 | 
				
			||||||
        padding: EdgeInsets.fromLTRB(
 | 
					 | 
				
			||||||
          16,
 | 
					 | 
				
			||||||
          8,
 | 
					 | 
				
			||||||
          16,
 | 
					 | 
				
			||||||
          16 + MediaQuery.of(context).padding.bottom,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        child: Row(
 | 
					 | 
				
			||||||
          children: [
 | 
					 | 
				
			||||||
            OutlinedButton.icon(
 | 
					 | 
				
			||||||
              onPressed: () {
 | 
					 | 
				
			||||||
                Navigator.of(context).maybePop();
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              icon: const Icon(Icons.close),
 | 
					 | 
				
			||||||
              label: const Text('Cancel'),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            const Spacer(),
 | 
					 | 
				
			||||||
            FilledButton.icon(
 | 
					 | 
				
			||||||
              onPressed: () {
 | 
					 | 
				
			||||||
                _submitPoll(context, ref);
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              icon: const Icon(Icons.cloud_upload_outlined),
 | 
					 | 
				
			||||||
              label: Text(model.id == null ? 'Create' : 'Update'),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,6 +92,7 @@ class PostDetailScreen extends HookConsumerWidget {
 | 
				
			|||||||
                  right: 0,
 | 
					                  right: 0,
 | 
				
			||||||
                  child: Material(
 | 
					                  child: Material(
 | 
				
			||||||
                    elevation: 2,
 | 
					                    elevation: 2,
 | 
				
			||||||
 | 
					                    color: Theme.of(context).colorScheme.surfaceContainer,
 | 
				
			||||||
                    child: postState
 | 
					                    child: postState
 | 
				
			||||||
                        .when(
 | 
					                        .when(
 | 
				
			||||||
                          data:
 | 
					                          data:
 | 
				
			||||||
@@ -107,8 +108,8 @@ class PostDetailScreen extends HookConsumerWidget {
 | 
				
			|||||||
                          error: (_, _) => const SizedBox.shrink(),
 | 
					                          error: (_, _) => const SizedBox.shrink(),
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        .padding(
 | 
					                        .padding(
 | 
				
			||||||
                          bottom: MediaQuery.of(context).padding.bottom + 16,
 | 
					                          bottom: MediaQuery.of(context).padding.bottom + 8,
 | 
				
			||||||
                          top: 16,
 | 
					                          top: 8,
 | 
				
			||||||
                          horizontal: 16,
 | 
					                          horizontal: 16,
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,28 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					import 'dart:developer';
 | 
				
			||||||
 | 
					import 'dart:io';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:dio/dio.dart';
 | 
					import 'package:dio/dio.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_app_update/azhon_app_update.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_app_update/update_model.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/markdown.dart';
 | 
				
			||||||
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
import 'package:package_info_plus/package_info_plus.dart';
 | 
					import 'package:package_info_plus/package_info_plus.dart';
 | 
				
			||||||
 | 
					import 'package:collection/collection.dart'; // Added for firstWhereOrNull
 | 
				
			||||||
import 'package:styled_widget/styled_widget.dart';
 | 
					import 'package:styled_widget/styled_widget.dart';
 | 
				
			||||||
import 'package:url_launcher/url_launcher.dart';
 | 
					import 'package:url_launcher/url_launcher.dart';
 | 
				
			||||||
import 'package:island/widgets/content/sheet.dart';
 | 
					import 'package:island/widgets/content/sheet.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Data model for a GitHub release we care about
 | 
					/// Data model for a GitHub release we care about
 | 
				
			||||||
class GithubReleaseInfo {
 | 
					class GithubReleaseInfo {
 | 
				
			||||||
  final String tagName; // e.g. 3.1.0+118
 | 
					  final String tagName;
 | 
				
			||||||
  final String name; // release title
 | 
					  final String name;
 | 
				
			||||||
  final String body; // changelog markdown
 | 
					  final String body;
 | 
				
			||||||
  final String htmlUrl; // release page
 | 
					  final String htmlUrl;
 | 
				
			||||||
  final DateTime createdAt;
 | 
					  final DateTime createdAt;
 | 
				
			||||||
 | 
					  final List<GithubReleaseAsset> assets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const GithubReleaseInfo({
 | 
					  const GithubReleaseInfo({
 | 
				
			||||||
    required this.tagName,
 | 
					    required this.tagName,
 | 
				
			||||||
@@ -21,9 +30,28 @@ class GithubReleaseInfo {
 | 
				
			|||||||
    required this.body,
 | 
					    required this.body,
 | 
				
			||||||
    required this.htmlUrl,
 | 
					    required this.htmlUrl,
 | 
				
			||||||
    required this.createdAt,
 | 
					    required this.createdAt,
 | 
				
			||||||
 | 
					    this.assets = const [],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Data model for a GitHub release asset
 | 
				
			||||||
 | 
					class GithubReleaseAsset {
 | 
				
			||||||
 | 
					  final String name;
 | 
				
			||||||
 | 
					  final String browserDownloadUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const GithubReleaseAsset({
 | 
				
			||||||
 | 
					    required this.name,
 | 
				
			||||||
 | 
					    required this.browserDownloadUrl,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory GithubReleaseAsset.fromJson(Map<String, dynamic> json) {
 | 
				
			||||||
 | 
					    return GithubReleaseAsset(
 | 
				
			||||||
 | 
					      name: json['name'] as String,
 | 
				
			||||||
 | 
					      browserDownloadUrl: json['browser_download_url'] as String,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Parses version and build number from "x.y.z+build"
 | 
					/// Parses version and build number from "x.y.z+build"
 | 
				
			||||||
class _ParsedVersion implements Comparable<_ParsedVersion> {
 | 
					class _ParsedVersion implements Comparable<_ParsedVersion> {
 | 
				
			||||||
  final int major;
 | 
					  final int major;
 | 
				
			||||||
@@ -62,7 +90,7 @@ class _ParsedVersion implements Comparable<_ParsedVersion> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UpdateService {
 | 
					class UpdateService {
 | 
				
			||||||
  UpdateService({Dio? dio})
 | 
					  UpdateService({Dio? dio, this.useProxy = false})
 | 
				
			||||||
    : _dio =
 | 
					    : _dio =
 | 
				
			||||||
          dio ??
 | 
					          dio ??
 | 
				
			||||||
          Dio(
 | 
					          Dio(
 | 
				
			||||||
@@ -78,6 +106,9 @@ class UpdateService {
 | 
				
			|||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final Dio _dio;
 | 
					  final Dio _dio;
 | 
				
			||||||
 | 
					  final bool useProxy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const _proxyBaseUrl = 'https://ghfast.top/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static const _releasesLatestApi =
 | 
					  static const _releasesLatestApi =
 | 
				
			||||||
      'https://api.github.com/repos/solsynth/solian/releases/latest';
 | 
					      'https://api.github.com/repos/solsynth/solian/releases/latest';
 | 
				
			||||||
@@ -85,31 +116,52 @@ class UpdateService {
 | 
				
			|||||||
  /// Checks GitHub for the latest release and compares against the current app version.
 | 
					  /// Checks GitHub for the latest release and compares against the current app version.
 | 
				
			||||||
  /// If update is available, shows a bottom sheet with changelog and an action to open release page.
 | 
					  /// If update is available, shows a bottom sheet with changelog and an action to open release page.
 | 
				
			||||||
  Future<void> checkForUpdates(BuildContext context) async {
 | 
					  Future<void> checkForUpdates(BuildContext context) async {
 | 
				
			||||||
 | 
					    log('[Update] Checking for updates...');
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      final release = await fetchLatestRelease();
 | 
					      final release = await fetchLatestRelease();
 | 
				
			||||||
      if (release == null) return;
 | 
					      if (release == null) {
 | 
				
			||||||
 | 
					        log('[Update] No latest release found or could not fetch.');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      log('[Update] Fetched latest release: ${release.tagName}');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      final info = await PackageInfo.fromPlatform();
 | 
					      final info = await PackageInfo.fromPlatform();
 | 
				
			||||||
      final localVersionStr = '${info.version}+${info.buildNumber}';
 | 
					      final localVersionStr = '${info.version}+${info.buildNumber}';
 | 
				
			||||||
 | 
					      log('[Update] Local app version: $localVersionStr');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      final latest = _ParsedVersion.tryParse(release.tagName);
 | 
					      final latest = _ParsedVersion.tryParse(release.tagName);
 | 
				
			||||||
      final local = _ParsedVersion.tryParse(localVersionStr);
 | 
					      final local = _ParsedVersion.tryParse(localVersionStr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (latest == null || local == null) {
 | 
					      if (latest == null || local == null) {
 | 
				
			||||||
 | 
					        log(
 | 
				
			||||||
 | 
					          '[Update] Failed to parse versions. Latest: ${release.tagName}, Local: $localVersionStr',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        // If parsing fails, do nothing silently
 | 
					        // If parsing fails, do nothing silently
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      log('[Update] Parsed versions. Latest: $latest, Local: $local');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      final needsUpdate = latest.compareTo(local) > 0;
 | 
					      final needsUpdate = latest.compareTo(local) > 0;
 | 
				
			||||||
      if (!needsUpdate) return;
 | 
					      if (!needsUpdate) {
 | 
				
			||||||
 | 
					        log('[Update] App is up to date. No update needed.');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      log('[Update] Update available! Latest: $latest, Local: $local');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!context.mounted) return;
 | 
					      if (!context.mounted) {
 | 
				
			||||||
 | 
					        log('[Update] Context not mounted, cannot show update sheet.');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Delay to ensure UI is ready (if called at startup)
 | 
					      // Delay to ensure UI is ready (if called at startup)
 | 
				
			||||||
      await Future.delayed(const Duration(milliseconds: 100));
 | 
					      await Future.delayed(const Duration(milliseconds: 100));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await showUpdateSheet(context, release);
 | 
					      if (context.mounted) {
 | 
				
			||||||
    } catch (_) {
 | 
					        await showUpdateSheet(context, release);
 | 
				
			||||||
 | 
					        log('[Update] Update sheet shown.');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      log('[Update] Error checking for updates: $e');
 | 
				
			||||||
      // Ignore errors (network, api, etc.)
 | 
					      // Ignore errors (network, api, etc.)
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -126,25 +178,68 @@ class UpdateService {
 | 
				
			|||||||
      context: context,
 | 
					      context: context,
 | 
				
			||||||
      isScrollControlled: true,
 | 
					      isScrollControlled: true,
 | 
				
			||||||
      useRootNavigator: true,
 | 
					      useRootNavigator: true,
 | 
				
			||||||
      builder:
 | 
					      builder: (ctx) {
 | 
				
			||||||
          (ctx) => _UpdateSheet(
 | 
					        String? androidUpdateUrl;
 | 
				
			||||||
            release: release,
 | 
					        if (Platform.isAndroid) {
 | 
				
			||||||
            onOpen: () async {
 | 
					          androidUpdateUrl = _getAndroidUpdateUrl(release.assets);
 | 
				
			||||||
              final uri = Uri.parse(release.htmlUrl);
 | 
					        }
 | 
				
			||||||
              if (await canLaunchUrl(uri)) {
 | 
					        return _UpdateSheet(
 | 
				
			||||||
                await launchUrl(uri, mode: LaunchMode.externalApplication);
 | 
					          release: release,
 | 
				
			||||||
              }
 | 
					          onOpen: () async {
 | 
				
			||||||
            },
 | 
					            final uri = Uri.parse(release.htmlUrl);
 | 
				
			||||||
          ),
 | 
					            if (await canLaunchUrl(uri)) {
 | 
				
			||||||
 | 
					              await launchUrl(uri, mode: LaunchMode.externalApplication);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          androidUpdateUrl: androidUpdateUrl,
 | 
				
			||||||
 | 
					          useProxy: useProxy, // Pass the useProxy flag
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String? _getAndroidUpdateUrl(List<GithubReleaseAsset> assets) {
 | 
				
			||||||
 | 
					    final arm64 = assets.firstWhereOrNull(
 | 
				
			||||||
 | 
					      (asset) => asset.name == 'app-arm64-v8a-release.apk',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    final armeabi = assets.firstWhereOrNull(
 | 
				
			||||||
 | 
					      (asset) => asset.name == 'app-armeabi-v7a-release.apk',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    final x86_64 = assets.firstWhereOrNull(
 | 
				
			||||||
 | 
					      (asset) => asset.name == 'app-x86_64-release.apk',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Prioritize arm64, then armeabi, then x86_64
 | 
				
			||||||
 | 
					    if (arm64 != null) {
 | 
				
			||||||
 | 
					      return arm64.browserDownloadUrl;
 | 
				
			||||||
 | 
					    } else if (armeabi != null) {
 | 
				
			||||||
 | 
					      return armeabi.browserDownloadUrl;
 | 
				
			||||||
 | 
					    } else if (x86_64 != null) {
 | 
				
			||||||
 | 
					      return x86_64.browserDownloadUrl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Fetch the latest release info from GitHub.
 | 
					  /// Fetch the latest release info from GitHub.
 | 
				
			||||||
  /// Public so other screens (e.g., About) can manually trigger update checks.
 | 
					  /// Public so other screens (e.g., About) can manually trigger update checks.
 | 
				
			||||||
  Future<GithubReleaseInfo?> fetchLatestRelease() async {
 | 
					  Future<GithubReleaseInfo?> fetchLatestRelease() async {
 | 
				
			||||||
    final resp = await _dio.get(_releasesLatestApi);
 | 
					    final apiEndpoint =
 | 
				
			||||||
    if (resp.statusCode != 200) return null;
 | 
					        useProxy
 | 
				
			||||||
 | 
					            ? '$_proxyBaseUrl${Uri.encodeComponent(_releasesLatestApi)}'
 | 
				
			||||||
 | 
					            : _releasesLatestApi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log(
 | 
				
			||||||
 | 
					      '[Update] Fetching latest release from GitHub API: $apiEndpoint (Proxy: $useProxy)',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    final resp = await _dio.get(apiEndpoint);
 | 
				
			||||||
 | 
					    if (resp.statusCode != 200) {
 | 
				
			||||||
 | 
					      log(
 | 
				
			||||||
 | 
					        '[Update] Failed to fetch latest release. Status code: ${resp.statusCode}',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    final data = resp.data as Map<String, dynamic>;
 | 
					    final data = resp.data as Map<String, dynamic>;
 | 
				
			||||||
 | 
					    log('[Update] Successfully fetched release data.');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final tagName = (data['tag_name'] ?? '').toString();
 | 
					    final tagName = (data['tag_name'] ?? '').toString();
 | 
				
			||||||
    final name = (data['name'] ?? tagName).toString();
 | 
					    final name = (data['name'] ?? tagName).toString();
 | 
				
			||||||
@@ -152,25 +247,70 @@ class UpdateService {
 | 
				
			|||||||
    final htmlUrl = (data['html_url'] ?? '').toString();
 | 
					    final htmlUrl = (data['html_url'] ?? '').toString();
 | 
				
			||||||
    final createdAtStr = (data['created_at'] ?? '').toString();
 | 
					    final createdAtStr = (data['created_at'] ?? '').toString();
 | 
				
			||||||
    final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now();
 | 
					    final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now();
 | 
				
			||||||
 | 
					    final assetsData =
 | 
				
			||||||
 | 
					        (data['assets'] as List<dynamic>?)
 | 
				
			||||||
 | 
					            ?.map((e) => GithubReleaseAsset.fromJson(e as Map<String, dynamic>))
 | 
				
			||||||
 | 
					            .toList() ??
 | 
				
			||||||
 | 
					        [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (tagName.isEmpty || htmlUrl.isEmpty) return null;
 | 
					    if (tagName.isEmpty || htmlUrl.isEmpty) {
 | 
				
			||||||
 | 
					      log(
 | 
				
			||||||
 | 
					        '[Update] Missing tag_name or html_url in release data. TagName: "$tagName", HtmlUrl: "$htmlUrl"',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log('[Update] Returning GithubReleaseInfo for tag: $tagName');
 | 
				
			||||||
    return GithubReleaseInfo(
 | 
					    return GithubReleaseInfo(
 | 
				
			||||||
      tagName: tagName,
 | 
					      tagName: tagName,
 | 
				
			||||||
      name: name,
 | 
					      name: name,
 | 
				
			||||||
      body: body,
 | 
					      body: body,
 | 
				
			||||||
      htmlUrl: htmlUrl,
 | 
					      htmlUrl: htmlUrl,
 | 
				
			||||||
      createdAt: createdAt,
 | 
					      createdAt: createdAt,
 | 
				
			||||||
 | 
					      assets: assetsData,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _UpdateSheet extends StatelessWidget {
 | 
					class _UpdateSheet extends StatefulWidget {
 | 
				
			||||||
  const _UpdateSheet({required this.release, required this.onOpen});
 | 
					  const _UpdateSheet({
 | 
				
			||||||
 | 
					    required this.release,
 | 
				
			||||||
 | 
					    required this.onOpen,
 | 
				
			||||||
 | 
					    this.androidUpdateUrl,
 | 
				
			||||||
 | 
					    this.useProxy = false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String? androidUpdateUrl;
 | 
				
			||||||
 | 
					  final bool useProxy;
 | 
				
			||||||
  final GithubReleaseInfo release;
 | 
					  final GithubReleaseInfo release;
 | 
				
			||||||
  final VoidCallback onOpen;
 | 
					  final VoidCallback onOpen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<_UpdateSheet> createState() => _UpdateSheetState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _UpdateSheetState extends State<_UpdateSheet> {
 | 
				
			||||||
 | 
					  late bool _useProxy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    _useProxy = widget.useProxy;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _installUpdate(String url) async {
 | 
				
			||||||
 | 
					    final downloadUrl =
 | 
				
			||||||
 | 
					        _useProxy ? 'https://ghfast.top/${Uri.encodeComponent(url)}' : url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    UpdateModel model = UpdateModel(
 | 
				
			||||||
 | 
					      downloadUrl,
 | 
				
			||||||
 | 
					      "solian-update-${widget.release.tagName}.apk",
 | 
				
			||||||
 | 
					      "ic_launcher",
 | 
				
			||||||
 | 
					      'https://apps.apple.com/us/app/solian/id6499032345',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    AzhonAppUpdate.update(model);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    final theme = Theme.of(context);
 | 
					    final theme = Theme.of(context);
 | 
				
			||||||
@@ -186,8 +326,11 @@ class _UpdateSheet extends StatelessWidget {
 | 
				
			|||||||
            Column(
 | 
					            Column(
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
					              crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
                Text(release.name, style: theme.textTheme.titleMedium).bold(),
 | 
					                Text(
 | 
				
			||||||
                Text(release.tagName).fontSize(12),
 | 
					                  widget.release.name,
 | 
				
			||||||
 | 
					                  style: theme.textTheme.titleMedium,
 | 
				
			||||||
 | 
					                ).bold(),
 | 
				
			||||||
 | 
					                Text(widget.release.tagName).fontSize(12),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ).padding(vertical: 16, horizontal: 16),
 | 
					            ).padding(vertical: 16, horizontal: 16),
 | 
				
			||||||
            const Divider(height: 1),
 | 
					            const Divider(height: 1),
 | 
				
			||||||
@@ -197,21 +340,45 @@ class _UpdateSheet extends StatelessWidget {
 | 
				
			|||||||
                  horizontal: 16,
 | 
					                  horizontal: 16,
 | 
				
			||||||
                  vertical: 16,
 | 
					                  vertical: 16,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                child: SelectableText(
 | 
					                child: MarkdownTextContent(
 | 
				
			||||||
                  release.body.isEmpty
 | 
					                  content:
 | 
				
			||||||
                      ? 'No changelog provided.'
 | 
					                      widget.release.body.isEmpty
 | 
				
			||||||
                      : release.body,
 | 
					                          ? 'No changelog provided.'
 | 
				
			||||||
                  style: theme.textTheme.bodyMedium,
 | 
					                          : widget.release.body,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            if (!kIsWeb && Platform.isAndroid)
 | 
				
			||||||
 | 
					              SwitchListTile(
 | 
				
			||||||
 | 
					                title: const Text('Use GitHub Proxy for Download'),
 | 
				
			||||||
 | 
					                value: _useProxy,
 | 
				
			||||||
 | 
					                onChanged: (value) {
 | 
				
			||||||
 | 
					                  setState(() {
 | 
				
			||||||
 | 
					                    _useProxy = value;
 | 
				
			||||||
 | 
					                  });
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              ).padding(horizontal: 8),
 | 
				
			||||||
            Column(
 | 
					            Column(
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
                Row(
 | 
					                Row(
 | 
				
			||||||
 | 
					                  spacing: 8,
 | 
				
			||||||
                  children: [
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    if (!kIsWeb &&
 | 
				
			||||||
 | 
					                        Platform.isAndroid &&
 | 
				
			||||||
 | 
					                        widget.androidUpdateUrl != null)
 | 
				
			||||||
 | 
					                      Expanded(
 | 
				
			||||||
 | 
					                        child: FilledButton.icon(
 | 
				
			||||||
 | 
					                          onPressed: () {
 | 
				
			||||||
 | 
					                            log(widget.androidUpdateUrl!);
 | 
				
			||||||
 | 
					                            _installUpdate(widget.androidUpdateUrl!);
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
 | 
					                          icon: const Icon(Symbols.update),
 | 
				
			||||||
 | 
					                          label: const Text('Install update'),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
                    Expanded(
 | 
					                    Expanded(
 | 
				
			||||||
                      child: FilledButton.icon(
 | 
					                      child: FilledButton.icon(
 | 
				
			||||||
                        onPressed: onOpen,
 | 
					                        onPressed: widget.onOpen,
 | 
				
			||||||
                        icon: const Icon(Icons.open_in_new),
 | 
					                        icon: const Icon(Icons.open_in_new),
 | 
				
			||||||
                        label: const Text('Open release page'),
 | 
					                        label: const Text('Open release page'),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,7 +55,7 @@ class AccountStatusCreationSheet extends HookConsumerWidget {
 | 
				
			|||||||
            'attitude': attitude.value,
 | 
					            'attitude': attitude.value,
 | 
				
			||||||
            'is_invisible': isInvisible.value,
 | 
					            'is_invisible': isInvisible.value,
 | 
				
			||||||
            'is_not_disturb': isNotDisturb.value,
 | 
					            'is_not_disturb': isNotDisturb.value,
 | 
				
			||||||
            'cleared_at': clearedAt.value?.toIso8601String(),
 | 
					            'cleared_at': clearedAt.value?.toUtc().toIso8601String(),
 | 
				
			||||||
            if (labelController.text.isNotEmpty) 'label': labelController.text,
 | 
					            if (labelController.text.isNotEmpty) 'label': labelController.text,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
 | 
					          options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -331,7 +331,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
 | 
				
			|||||||
    final user = ref.watch(userInfoProvider);
 | 
					    final user = ref.watch(userInfoProvider);
 | 
				
			||||||
    final websocketState = ref.watch(websocketStateProvider);
 | 
					    final websocketState = ref.watch(websocketStateProvider);
 | 
				
			||||||
    final indicatorHeight =
 | 
					    final indicatorHeight =
 | 
				
			||||||
        MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 20);
 | 
					        MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 25);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Color indicatorColor;
 | 
					    Color indicatorColor;
 | 
				
			||||||
    String indicatorText;
 | 
					    String indicatorText;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			|||||||
import 'package:island/pods/websocket.dart';
 | 
					import 'package:island/pods/websocket.dart';
 | 
				
			||||||
import 'package:island/services/notify.dart';
 | 
					import 'package:island/services/notify.dart';
 | 
				
			||||||
import 'package:island/services/sharing_intent.dart';
 | 
					import 'package:island/services/sharing_intent.dart';
 | 
				
			||||||
 | 
					import 'package:island/services/update_service.dart';
 | 
				
			||||||
import 'package:island/widgets/content/network_status_sheet.dart';
 | 
					import 'package:island/widgets/content/network_status_sheet.dart';
 | 
				
			||||||
import 'package:island/widgets/tour/tour.dart';
 | 
					import 'package:island/widgets/tour/tour.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,6 +22,7 @@ class AppWrapper extends HookConsumerWidget {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
      final sharingService = SharingIntentService();
 | 
					      final sharingService = SharingIntentService();
 | 
				
			||||||
      sharingService.initialize(context);
 | 
					      sharingService.initialize(context);
 | 
				
			||||||
 | 
					      UpdateService().checkForUpdates(context);
 | 
				
			||||||
      return () {
 | 
					      return () {
 | 
				
			||||||
        sharingService.dispose();
 | 
					        sharingService.dispose();
 | 
				
			||||||
        ntySubs?.cancel();
 | 
					        ntySubs?.cancel();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -142,7 +142,7 @@ class CloudVideoWidget extends HookConsumerWidget {
 | 
				
			|||||||
              mainAxisAlignment: MainAxisAlignment.end,
 | 
					              mainAxisAlignment: MainAxisAlignment.end,
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
					              crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
                Row(
 | 
					                Wrap(
 | 
				
			||||||
                  spacing: 8,
 | 
					                  spacing: 8,
 | 
				
			||||||
                  children: [
 | 
					                  children: [
 | 
				
			||||||
                    if (item.fileMeta?['duration'] != null)
 | 
					                    if (item.fileMeta?['duration'] != null)
 | 
				
			||||||
@@ -199,8 +199,8 @@ class CloudVideoWidget extends HookConsumerWidget {
 | 
				
			|||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ).padding(horizontal: 16, bottom: 12),
 | 
				
			||||||
          ).padding(horizontal: 16, bottom: 12),
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      onTap: () {
 | 
					      onTap: () {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
 | 
				
			|||||||
import 'package:flutter_highlight/themes/a11y-dark.dart';
 | 
					import 'package:flutter_highlight/themes/a11y-dark.dart';
 | 
				
			||||||
import 'package:flutter_highlight/themes/a11y-light.dart';
 | 
					import 'package:flutter_highlight/themes/a11y-light.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
 | 
					import 'package:google_fonts/google_fonts.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:island/models/file.dart';
 | 
					import 'package:island/models/file.dart';
 | 
				
			||||||
import 'package:island/pods/config.dart';
 | 
					import 'package:island/pods/config.dart';
 | 
				
			||||||
@@ -71,7 +72,22 @@ class MarkdownTextContent extends HookConsumerWidget {
 | 
				
			|||||||
            textStyle: textStyle ?? Theme.of(context).textTheme.bodyMedium!,
 | 
					            textStyle: textStyle ?? Theme.of(context).textTheme.bodyMedium!,
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          HrConfig(height: 1, color: Theme.of(context).dividerColor),
 | 
					          HrConfig(height: 1, color: Theme.of(context).dividerColor),
 | 
				
			||||||
          PreConfig(theme: isDark ? a11yDarkTheme : a11yLightTheme),
 | 
					          PreConfig(
 | 
				
			||||||
 | 
					            theme: isDark ? a11yDarkTheme : a11yLightTheme,
 | 
				
			||||||
 | 
					            textStyle: GoogleFonts.robotoMono(fontSize: 14),
 | 
				
			||||||
 | 
					            styleNotMatched: GoogleFonts.robotoMono(fontSize: 14),
 | 
				
			||||||
 | 
					            decoration: BoxDecoration(
 | 
				
			||||||
 | 
					              color: Theme.of(context).colorScheme.surfaceContainerHighest,
 | 
				
			||||||
 | 
					              borderRadius: BorderRadius.all(Radius.circular(8.0)),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          TableConfig(
 | 
				
			||||||
 | 
					            wrapper:
 | 
				
			||||||
 | 
					                (child) => SingleChildScrollView(
 | 
				
			||||||
 | 
					                  scrollDirection: Axis.horizontal,
 | 
				
			||||||
 | 
					                  child: child,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
          LinkConfig(
 | 
					          LinkConfig(
 | 
				
			||||||
            style:
 | 
					            style:
 | 
				
			||||||
                linkStyle ??
 | 
					                linkStyle ??
 | 
				
			||||||
@@ -160,7 +176,7 @@ class MarkdownTextContent extends HookConsumerWidget {
 | 
				
			|||||||
                          uri: stickerUri,
 | 
					                          uri: stickerUri,
 | 
				
			||||||
                          width: size,
 | 
					                          width: size,
 | 
				
			||||||
                          height: size,
 | 
					                          height: size,
 | 
				
			||||||
                          fit: BoxFit.cover,
 | 
					                          fit: BoxFit.contain,
 | 
				
			||||||
                          noCacheOptimization: true,
 | 
					                          noCacheOptimization: true,
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										76
									
								
								lib/widgets/debug_sheet.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								lib/widgets/debug_sheet.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_cache_manager/flutter_cache_manager.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/message.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
 | 
					import 'package:island/pods/websocket.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/network_status_sheet.dart';
 | 
				
			||||||
 | 
					import 'package:island/widgets/content/sheet.dart';
 | 
				
			||||||
 | 
					import 'package:material_symbols_icons/symbols.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DebugSheet extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  const DebugSheet({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    final wsNotifier = ref.watch(websocketStateProvider.notifier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return SheetScaffold(
 | 
				
			||||||
 | 
					      titleText: 'Debug',
 | 
				
			||||||
 | 
					      child: Column(
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          ListTile(
 | 
				
			||||||
 | 
					            minTileHeight: 48,
 | 
				
			||||||
 | 
					            leading: const Icon(Symbols.wifi),
 | 
				
			||||||
 | 
					            trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					            title: Text('Connection Status'),
 | 
				
			||||||
 | 
					            contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					            onTap: () {
 | 
				
			||||||
 | 
					              showModalBottomSheet(
 | 
				
			||||||
 | 
					                context: context,
 | 
				
			||||||
 | 
					                isScrollControlled: true,
 | 
				
			||||||
 | 
					                builder:
 | 
				
			||||||
 | 
					                    (context) => NetworkStatusSheet(
 | 
				
			||||||
 | 
					                      onReconnect: () => wsNotifier.connect(),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          const Divider(height: 1),
 | 
				
			||||||
 | 
					          ListTile(
 | 
				
			||||||
 | 
					            minTileHeight: 48,
 | 
				
			||||||
 | 
					            leading: const Icon(Symbols.copy_all),
 | 
				
			||||||
 | 
					            trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					            contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					            title: Text('Copy access token'),
 | 
				
			||||||
 | 
					            onTap: () async {
 | 
				
			||||||
 | 
					              final tk = ref.watch(tokenProvider);
 | 
				
			||||||
 | 
					              Clipboard.setData(ClipboardData(text: tk!.token));
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          ListTile(
 | 
				
			||||||
 | 
					            minTileHeight: 48,
 | 
				
			||||||
 | 
					            leading: const Icon(Symbols.delete),
 | 
				
			||||||
 | 
					            trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					            contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					            title: Text('Reset database'),
 | 
				
			||||||
 | 
					            onTap: () async {
 | 
				
			||||||
 | 
					              resetDatabase(ref);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          ListTile(
 | 
				
			||||||
 | 
					            minTileHeight: 48,
 | 
				
			||||||
 | 
					            leading: const Icon(Symbols.clear),
 | 
				
			||||||
 | 
					            trailing: const Icon(Symbols.chevron_right),
 | 
				
			||||||
 | 
					            contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					            title: Text('Clear cache'),
 | 
				
			||||||
 | 
					            onTap: () async {
 | 
				
			||||||
 | 
					              DefaultCacheManager().emptyCache();
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -248,7 +248,7 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
      final client = ref.read(apiClientProvider);
 | 
					      final client = ref.read(apiClientProvider);
 | 
				
			||||||
      final response = await client.post(
 | 
					      final response = await client.post(
 | 
				
			||||||
        '/orders/${widget.order.id}/pay',
 | 
					        '/id/orders/${widget.order.id}/pay',
 | 
				
			||||||
        data: {'pin_code': pin},
 | 
					        data: {'pin_code': pin},
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -273,7 +273,7 @@ class PostItem extends HookConsumerWidget {
 | 
				
			|||||||
            : item.reactionsCount.entries
 | 
					            : item.reactionsCount.entries
 | 
				
			||||||
                .sortedBy((e) => e.value)
 | 
					                .sortedBy((e) => e.value)
 | 
				
			||||||
                .map((e) => e.key)
 | 
					                .map((e) => e.key)
 | 
				
			||||||
                .first;
 | 
					                .last;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final postLanguage =
 | 
					    final postLanguage =
 | 
				
			||||||
        item.content != null
 | 
					        item.content != null
 | 
				
			||||||
@@ -480,7 +480,9 @@ class PostItem extends HookConsumerWidget {
 | 
				
			|||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        else if (item.content?.isNotEmpty ?? false)
 | 
					        else if ((item.content?.isNotEmpty ?? false) ||
 | 
				
			||||||
 | 
					            (item.title?.isNotEmpty ?? false) ||
 | 
				
			||||||
 | 
					            (item.description?.isNotEmpty ?? false))
 | 
				
			||||||
          Padding(
 | 
					          Padding(
 | 
				
			||||||
            padding: EdgeInsets.only(
 | 
					            padding: EdgeInsets.only(
 | 
				
			||||||
              left: renderingPadding.horizontal,
 | 
					              left: renderingPadding.horizontal,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,13 @@
 | 
				
			|||||||
import 'package:dio/dio.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:island/models/post.dart';
 | 
					import 'package:island/models/post.dart';
 | 
				
			||||||
import 'package:island/models/publisher.dart';
 | 
					import 'package:island/models/publisher.dart';
 | 
				
			||||||
import 'package:island/pods/network.dart';
 | 
					import 'package:island/pods/network.dart';
 | 
				
			||||||
import 'package:island/screens/creators/publishers.dart';
 | 
					import 'package:island/screens/creators/publishers.dart';
 | 
				
			||||||
 | 
					import 'package:island/screens/posts/compose.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/post/publishers_modal.dart';
 | 
					import 'package:island/widgets/post/publishers_modal.dart';
 | 
				
			||||||
@@ -14,8 +16,14 @@ import 'package:styled_widget/styled_widget.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class PostQuickReply extends HookConsumerWidget {
 | 
					class PostQuickReply extends HookConsumerWidget {
 | 
				
			||||||
  final SnPost parent;
 | 
					  final SnPost parent;
 | 
				
			||||||
  final Function? onPosted;
 | 
					  final VoidCallback? onPosted;
 | 
				
			||||||
  const PostQuickReply({super.key, required this.parent, this.onPosted});
 | 
					  final VoidCallback? onLaunch;
 | 
				
			||||||
 | 
					  const PostQuickReply({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.parent,
 | 
				
			||||||
 | 
					    this.onPosted,
 | 
				
			||||||
 | 
					    this.onLaunch,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
@@ -48,7 +56,7 @@ class PostQuickReply extends HookConsumerWidget {
 | 
				
			|||||||
            'content': contentController.text,
 | 
					            'content': contentController.text,
 | 
				
			||||||
            'replied_post_id': parent.id,
 | 
					            'replied_post_id': parent.id,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          options: Options(headers: {'X-Pub': currentPublisher.value?.name}),
 | 
					          queryParameters: {'pub': currentPublisher.value?.name},
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        contentController.clear();
 | 
					        contentController.clear();
 | 
				
			||||||
        onPosted?.call();
 | 
					        onPosted?.call();
 | 
				
			||||||
@@ -83,9 +91,10 @@ class PostQuickReply extends HookConsumerWidget {
 | 
				
			|||||||
                child: TextField(
 | 
					                child: TextField(
 | 
				
			||||||
                  controller: contentController,
 | 
					                  controller: contentController,
 | 
				
			||||||
                  decoration: InputDecoration(
 | 
					                  decoration: InputDecoration(
 | 
				
			||||||
                    hintText: 'Post your reply',
 | 
					                    hintText: 'postReplyPlaceholder'.tr(),
 | 
				
			||||||
                    border: const OutlineInputBorder(),
 | 
					                    border: InputBorder.none,
 | 
				
			||||||
                    isDense: true,
 | 
					                    isDense: true,
 | 
				
			||||||
 | 
					                    isCollapsed: true,
 | 
				
			||||||
                    contentPadding: EdgeInsets.symmetric(
 | 
					                    contentPadding: EdgeInsets.symmetric(
 | 
				
			||||||
                      horizontal: 12,
 | 
					                      horizontal: 12,
 | 
				
			||||||
                      vertical: 8,
 | 
					                      vertical: 8,
 | 
				
			||||||
@@ -97,6 +106,26 @@ class PostQuickReply extends HookConsumerWidget {
 | 
				
			|||||||
                      (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
					                      (_) => FocusManager.instance.primaryFocus?.unfocus(),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 | 
					              IconButton(
 | 
				
			||||||
 | 
					                onPressed: () {
 | 
				
			||||||
 | 
					                  onLaunch?.call();
 | 
				
			||||||
 | 
					                  GoRouter.of(context)
 | 
				
			||||||
 | 
					                      .pushNamed(
 | 
				
			||||||
 | 
					                        'postCompose',
 | 
				
			||||||
 | 
					                        extra: PostComposeInitialState(
 | 
				
			||||||
 | 
					                          content: contentController.text,
 | 
				
			||||||
 | 
					                          replyingTo: parent,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      )
 | 
				
			||||||
 | 
					                      .then((value) {
 | 
				
			||||||
 | 
					                        if (value != null) onPosted?.call();
 | 
				
			||||||
 | 
					                      });
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                icon: const Icon(Symbols.launch, size: 20),
 | 
				
			||||||
 | 
					                padding: EdgeInsets.zero,
 | 
				
			||||||
 | 
					                visualDensity: VisualDensity.compact,
 | 
				
			||||||
 | 
					                constraints: const BoxConstraints(),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
              IconButton(
 | 
					              IconButton(
 | 
				
			||||||
                padding: EdgeInsets.zero,
 | 
					                padding: EdgeInsets.zero,
 | 
				
			||||||
                visualDensity: VisualDensity.compact,
 | 
					                visualDensity: VisualDensity.compact,
 | 
				
			||||||
@@ -110,6 +139,7 @@ class PostQuickReply extends HookConsumerWidget {
 | 
				
			|||||||
                        : Icon(Symbols.send, size: 20),
 | 
					                        : Icon(Symbols.send, size: 20),
 | 
				
			||||||
                color: Theme.of(context).colorScheme.primary,
 | 
					                color: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
                onPressed: submitting.value ? null : performAction,
 | 
					                onPressed: submitting.value ? null : performAction,
 | 
				
			||||||
 | 
					                constraints: const BoxConstraints(),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,14 +38,18 @@ class PostRepliesSheet extends HookConsumerWidget {
 | 
				
			|||||||
          if (user.value != null)
 | 
					          if (user.value != null)
 | 
				
			||||||
            Material(
 | 
					            Material(
 | 
				
			||||||
              elevation: 2,
 | 
					              elevation: 2,
 | 
				
			||||||
 | 
					              color: Theme.of(context).colorScheme.surfaceContainerHigh,
 | 
				
			||||||
              child: PostQuickReply(
 | 
					              child: PostQuickReply(
 | 
				
			||||||
                parent: post,
 | 
					                parent: post,
 | 
				
			||||||
                onPosted: () {
 | 
					                onPosted: () {
 | 
				
			||||||
                  ref.invalidate(postRepliesNotifierProvider(post.id));
 | 
					                  ref.invalidate(postRepliesNotifierProvider(post.id));
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                onLaunch: () {
 | 
				
			||||||
 | 
					                  Navigator.of(context).pop();
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
              ).padding(
 | 
					              ).padding(
 | 
				
			||||||
                bottom: MediaQuery.of(context).padding.bottom + 16,
 | 
					                bottom: MediaQuery.of(context).padding.bottom + 8,
 | 
				
			||||||
                top: 16,
 | 
					                top: 8,
 | 
				
			||||||
                horizontal: 16,
 | 
					                horizontal: 16,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -130,25 +130,25 @@ PODS:
 | 
				
			|||||||
  - sqflite_darwin (0.0.4):
 | 
					  - sqflite_darwin (0.0.4):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
  - sqlite3 (3.50.3):
 | 
					  - sqlite3 (3.50.4):
 | 
				
			||||||
    - sqlite3/common (= 3.50.3)
 | 
					    - sqlite3/common (= 3.50.4)
 | 
				
			||||||
  - sqlite3/common (3.50.3)
 | 
					  - sqlite3/common (3.50.4)
 | 
				
			||||||
  - sqlite3/dbstatvtab (3.50.3):
 | 
					  - sqlite3/dbstatvtab (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/fts5 (3.50.3):
 | 
					  - sqlite3/fts5 (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/math (3.50.3):
 | 
					  - sqlite3/math (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/perf-threadsafe (3.50.3):
 | 
					  - sqlite3/perf-threadsafe (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/rtree (3.50.3):
 | 
					  - sqlite3/rtree (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3/session (3.50.3):
 | 
					  - sqlite3/session (3.50.4):
 | 
				
			||||||
    - sqlite3/common
 | 
					    - sqlite3/common
 | 
				
			||||||
  - sqlite3_flutter_libs (0.0.1):
 | 
					  - sqlite3_flutter_libs (0.0.1):
 | 
				
			||||||
    - Flutter
 | 
					    - Flutter
 | 
				
			||||||
    - FlutterMacOS
 | 
					    - FlutterMacOS
 | 
				
			||||||
    - sqlite3 (~> 3.50.3)
 | 
					    - sqlite3 (~> 3.50.4)
 | 
				
			||||||
    - sqlite3/dbstatvtab
 | 
					    - sqlite3/dbstatvtab
 | 
				
			||||||
    - sqlite3/fts5
 | 
					    - sqlite3/fts5
 | 
				
			||||||
    - sqlite3/math
 | 
					    - sqlite3/math
 | 
				
			||||||
@@ -328,8 +328,8 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
  shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
 | 
					  shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
 | 
				
			||||||
  sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
 | 
					  sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
 | 
				
			||||||
  sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
 | 
					  sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
 | 
				
			||||||
  sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
 | 
					  sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
 | 
				
			||||||
  sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
 | 
					  sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
 | 
				
			||||||
  super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
 | 
					  super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
 | 
				
			||||||
  url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
 | 
					  url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
 | 
				
			||||||
  volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
 | 
					  volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										80
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								pubspec.lock
									
									
									
									
									
								
							@@ -73,22 +73,6 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.13.0"
 | 
					    version: "2.13.0"
 | 
				
			||||||
  auto_route:
 | 
					 | 
				
			||||||
    dependency: transitive
 | 
					 | 
				
			||||||
    description:
 | 
					 | 
				
			||||||
      name: auto_route
 | 
					 | 
				
			||||||
      sha256: b8c036fa613a98a759cf0fdcba26e62f4985dcbff01a5e760ab411e8554bbaf0
 | 
					 | 
				
			||||||
      url: "https://pub.dev"
 | 
					 | 
				
			||||||
    source: hosted
 | 
					 | 
				
			||||||
    version: "10.1.0+1"
 | 
					 | 
				
			||||||
  auto_route_generator:
 | 
					 | 
				
			||||||
    dependency: "direct dev"
 | 
					 | 
				
			||||||
    description:
 | 
					 | 
				
			||||||
      name: auto_route_generator
 | 
					 | 
				
			||||||
      sha256: "9e3846fcbeacba5c362557328dd8c8fbc953b6a0cbc3395365e8d8f92eea29c4"
 | 
					 | 
				
			||||||
      url: "https://pub.dev"
 | 
					 | 
				
			||||||
    source: hosted
 | 
					 | 
				
			||||||
    version: "10.1.0"
 | 
					 | 
				
			||||||
  avatar_stack:
 | 
					  avatar_stack:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -205,10 +189,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: built_value
 | 
					      name: built_value
 | 
				
			||||||
      sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62"
 | 
					      sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "8.11.0"
 | 
					    version: "8.11.1"
 | 
				
			||||||
  cached_network_image:
 | 
					  cached_network_image:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -453,10 +437,10 @@ packages:
 | 
				
			|||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: dio
 | 
					      name: dio
 | 
				
			||||||
      sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
 | 
					      sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "5.8.0+1"
 | 
					    version: "5.9.0"
 | 
				
			||||||
  dio_web_adapter:
 | 
					  dio_web_adapter:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -573,10 +557,10 @@ packages:
 | 
				
			|||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: file_picker
 | 
					      name: file_picker
 | 
				
			||||||
      sha256: "13ba4e627ef24503a465d1d61b32596ce10eb6b8903678d362a528f9939b4aa8"
 | 
					      sha256: "8f9f429998f9232d65bc4757af74475ce44fc80f10704ff5dfa8b1d14fc429b9"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "10.2.1"
 | 
					    version: "10.2.3"
 | 
				
			||||||
  file_selector_linux:
 | 
					  file_selector_linux:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -678,6 +662,14 @@ packages:
 | 
				
			|||||||
    description: flutter
 | 
					    description: flutter
 | 
				
			||||||
    source: sdk
 | 
					    source: sdk
 | 
				
			||||||
    version: "0.0.0"
 | 
					    version: "0.0.0"
 | 
				
			||||||
 | 
					  flutter_app_update:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: flutter_app_update
 | 
				
			||||||
 | 
					      sha256: "09290240949c4651581cd6fc535e52d019e189e694d6019c56b5a56c2e69ba65"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "3.2.2"
 | 
				
			||||||
  flutter_blurhash:
 | 
					  flutter_blurhash:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -911,10 +903,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: flutter_plugin_android_lifecycle
 | 
					      name: flutter_plugin_android_lifecycle
 | 
				
			||||||
      sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e
 | 
					      sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.0.28"
 | 
					    version: "2.0.29"
 | 
				
			||||||
  flutter_popup_card:
 | 
					  flutter_popup_card:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1033,10 +1025,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: font_awesome_flutter
 | 
					      name: font_awesome_flutter
 | 
				
			||||||
      sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a
 | 
					      sha256: f50ce90dbe26d977415b9540400d6778bef00894aced6358ae578abd92b14b10
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "10.8.0"
 | 
					    version: "10.9.0"
 | 
				
			||||||
  freezed:
 | 
					  freezed:
 | 
				
			||||||
    dependency: "direct dev"
 | 
					    dependency: "direct dev"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1089,10 +1081,10 @@ packages:
 | 
				
			|||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: go_router
 | 
					      name: go_router
 | 
				
			||||||
      sha256: c489908a54ce2131f1d1b7cc631af9c1a06fac5ca7c449e959192089f9489431
 | 
					      sha256: "8b1f37dfaf6e958c6b872322db06f946509433bec3de753c3491a42ae9ec2b48"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "16.0.0"
 | 
					    version: "16.1.0"
 | 
				
			||||||
  google_fonts:
 | 
					  google_fonts:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1153,10 +1145,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: http
 | 
					      name: http
 | 
				
			||||||
      sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
 | 
					      sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.4.0"
 | 
					    version: "1.5.0"
 | 
				
			||||||
  http_multi_server:
 | 
					  http_multi_server:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1193,10 +1185,10 @@ packages:
 | 
				
			|||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: image_picker_android
 | 
					      name: image_picker_android
 | 
				
			||||||
      sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d"
 | 
					      sha256: b08e9a04d0f8d91f4a6e767a745b9871bfbc585410205c311d0492de20a7ccd6
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.8.12+24"
 | 
					    version: "0.8.12+25"
 | 
				
			||||||
  image_picker_for_web:
 | 
					  image_picker_for_web:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -1361,18 +1353,18 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: local_auth_android
 | 
					      name: local_auth_android
 | 
				
			||||||
      sha256: "82b2bdeee2199a510d3b7716121e96a6609da86693bb0863edd8566355406b79"
 | 
					      sha256: "316503f6772dea9c0c038bb7aac4f68ab00112d707d258c770f7fc3c250a2d88"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.0.50"
 | 
					    version: "1.0.51"
 | 
				
			||||||
  local_auth_darwin:
 | 
					  local_auth_darwin:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: local_auth_darwin
 | 
					      name: local_auth_darwin
 | 
				
			||||||
      sha256: "25163ce60a5a6c468cf7a0e3dc8a165f824cabc2aa9e39a5e9fc5c2311b7686f"
 | 
					      sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "1.5.0"
 | 
					    version: "1.6.0"
 | 
				
			||||||
  local_auth_platform_interface:
 | 
					  local_auth_platform_interface:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -2033,10 +2025,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: shared_preferences_android
 | 
					      name: shared_preferences_android
 | 
				
			||||||
      sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
 | 
					      sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.4.10"
 | 
					    version: "2.4.11"
 | 
				
			||||||
  shared_preferences_foundation:
 | 
					  shared_preferences_foundation:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -2206,18 +2198,18 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: sqlite3
 | 
					      name: sqlite3
 | 
				
			||||||
      sha256: dd806fff004a0aeb01e208b858dbc649bc72104670d425a81a6dd17698535f6e
 | 
					      sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.8.0"
 | 
					    version: "2.9.0"
 | 
				
			||||||
  sqlite3_flutter_libs:
 | 
					  sqlite3_flutter_libs:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: sqlite3_flutter_libs
 | 
					      name: sqlite3_flutter_libs
 | 
				
			||||||
      sha256: fd996da5515a73aacd0a04ae7063db5fe8df42670d974df4c3ee538c652eef2e
 | 
					      sha256: "2b03273e71867a8a4d030861fc21706200debe5c5858a4b9e58f4a1c129586a4"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.5.38"
 | 
					    version: "0.5.39"
 | 
				
			||||||
  sqlparser:
 | 
					  sqlparser:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -2424,10 +2416,10 @@ packages:
 | 
				
			|||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
      name: url_launcher_android
 | 
					      name: url_launcher_android
 | 
				
			||||||
      sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
 | 
					      sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656"
 | 
				
			||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "6.3.16"
 | 
					    version: "6.3.17"
 | 
				
			||||||
  url_launcher_ios:
 | 
					  url_launcher_ios:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								pubspec.yaml
									
									
									
									
									
								
							@@ -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.1.0+117
 | 
					version: 3.1.0+121
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.7.2
 | 
					  sdk: ^3.7.2
 | 
				
			||||||
@@ -39,12 +39,12 @@ 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: ^16.0.0
 | 
					  go_router: ^16.1.0
 | 
				
			||||||
  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
 | 
				
			||||||
  path_provider: ^2.1.5
 | 
					  path_provider: ^2.1.5
 | 
				
			||||||
  dio: ^5.8.0+1
 | 
					  dio: ^5.9.0
 | 
				
			||||||
  very_good_infinite_list: ^0.9.0
 | 
					  very_good_infinite_list: ^0.9.0
 | 
				
			||||||
  freezed_annotation: ^3.1.0
 | 
					  freezed_annotation: ^3.1.0
 | 
				
			||||||
  json_annotation: ^4.9.0
 | 
					  json_annotation: ^4.9.0
 | 
				
			||||||
@@ -73,10 +73,10 @@ dependencies:
 | 
				
			|||||||
    git: https://github.com/LittleSheep2Code/tus_client.git
 | 
					    git: https://github.com/LittleSheep2Code/tus_client.git
 | 
				
			||||||
  cross_file: ^0.3.4+2
 | 
					  cross_file: ^0.3.4+2
 | 
				
			||||||
  image_picker: ^1.1.2
 | 
					  image_picker: ^1.1.2
 | 
				
			||||||
  file_picker: ^10.2.1
 | 
					  file_picker: ^10.2.3
 | 
				
			||||||
  riverpod_annotation: ^2.6.1
 | 
					  riverpod_annotation: ^2.6.1
 | 
				
			||||||
  image_picker_platform_interface: ^2.10.1
 | 
					  image_picker_platform_interface: ^2.10.1
 | 
				
			||||||
  image_picker_android: ^0.8.12+24
 | 
					  image_picker_android: ^0.8.12+25
 | 
				
			||||||
  super_context_menu: ^0.9.1
 | 
					  super_context_menu: ^0.9.1
 | 
				
			||||||
  modal_bottom_sheet: ^3.0.0
 | 
					  modal_bottom_sheet: ^3.0.0
 | 
				
			||||||
  firebase_messaging: ^16.0.0
 | 
					  firebase_messaging: ^16.0.0
 | 
				
			||||||
@@ -133,6 +133,7 @@ dependencies:
 | 
				
			|||||||
  flutter_typeahead: ^5.2.0
 | 
					  flutter_typeahead: ^5.2.0
 | 
				
			||||||
  flutter_langdetect: ^0.0.2
 | 
					  flutter_langdetect: ^0.0.2
 | 
				
			||||||
  waveform_flutter: ^1.2.0
 | 
					  waveform_flutter: ^1.2.0
 | 
				
			||||||
 | 
					  flutter_app_update: ^3.2.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev_dependencies:
 | 
					dev_dependencies:
 | 
				
			||||||
  flutter_test:
 | 
					  flutter_test:
 | 
				
			||||||
@@ -144,7 +145,6 @@ dev_dependencies:
 | 
				
			|||||||
  # package. See that file for information about deactivating specific lint
 | 
					  # package. See that file for information about deactivating specific lint
 | 
				
			||||||
  # rules and activating additional ones.
 | 
					  # rules and activating additional ones.
 | 
				
			||||||
  flutter_lints: ^6.0.0
 | 
					  flutter_lints: ^6.0.0
 | 
				
			||||||
  auto_route_generator: ^10.1.0
 | 
					 | 
				
			||||||
  build_runner: ^2.5.4
 | 
					  build_runner: ^2.5.4
 | 
				
			||||||
  freezed: ^3.1.0
 | 
					  freezed: ^3.1.0
 | 
				
			||||||
  json_serializable: ^6.9.5
 | 
					  json_serializable: ^6.9.5
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user