Compare commits
	
		
			7 Commits
		
	
	
		
			9bdf8ba346
			...
			3.1.0+120
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6558854a7a | |||
| 892035ab27 | |||
| 87ae8d2ff4 | |||
| 15c2dbaa0d | |||
| 6b3338b885 | |||
| bb00b1bc6a | |||
| 5e1a15ada2 | 
| @@ -786,5 +786,6 @@ | ||||
|   "links": "Links", | ||||
|   "addLink": "Add link", | ||||
|   "linkKey": "Link Name", | ||||
|   "linkValue": "URL" | ||||
|   "linkValue": "URL", | ||||
|   "debugOptions": "Debug Options" | ||||
| } | ||||
|   | ||||
| @@ -1,14 +1,26 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:island/models/publisher.dart'; | ||||
|  | ||||
| part 'developer.freezed.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 | ||||
| sealed class DeveloperStats with _$DeveloperStats { | ||||
|   const factory DeveloperStats({ | ||||
|     @Default(0) int totalCustomApps, | ||||
|   }) = _DeveloperStats; | ||||
|   const factory DeveloperStats({@Default(0) int totalCustomApps}) = | ||||
|       _DeveloperStats; | ||||
|  | ||||
|   factory DeveloperStats.fromJson(Map<String, dynamic> json) => | ||||
|       _$DeveloperStatsFromJson(json); | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,293 @@ part of 'developer.dart'; | ||||
| // dart format off | ||||
| 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 | ||||
| mixin _$DeveloperStats { | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,22 @@ part of 'developer.dart'; | ||||
| // 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( | ||||
|       totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0, | ||||
|   | ||||
| @@ -102,235 +102,243 @@ class _AboutScreenState extends ConsumerState<AboutScreen> { | ||||
|               ? const Center(child: CircularProgressIndicator()) | ||||
|               : _errorMessage != null | ||||
|               ? Center(child: Text(_errorMessage!)) | ||||
|               : SingleChildScrollView( | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                   children: [ | ||||
|                     const SizedBox(height: 24), | ||||
|                     // App Icon and Name | ||||
|                     CircleAvatar( | ||||
|                       radius: 50, | ||||
|                       backgroundColor: theme.colorScheme.primary.withOpacity( | ||||
|                         0.1, | ||||
|                       ), | ||||
|                       child: Image.asset( | ||||
|                         'assets/icons/icon.png', | ||||
|                         width: 56, | ||||
|                         height: 56, | ||||
|                       ), | ||||
|                     ), | ||||
|                     const SizedBox(height: 16), | ||||
|                     Text( | ||||
|                       _packageInfo.appName, | ||||
|                       style: theme.textTheme.headlineSmall?.copyWith( | ||||
|                         fontWeight: FontWeight.bold, | ||||
|                       ), | ||||
|                     ), | ||||
|                     Text( | ||||
|                       '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(), | ||||
|               : Center( | ||||
|                 child: ConstrainedBox( | ||||
|                   constraints: const BoxConstraints(maxWidth: 540), | ||||
|                   child: SingleChildScrollView( | ||||
|                     child: Column( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                       children: [ | ||||
|                         _buildInfoItem( | ||||
|                           context, | ||||
|                           icon: Symbols.info, | ||||
|                           label: 'aboutScreenPackageNameLabel'.tr(), | ||||
|                           value: _packageInfo.packageName, | ||||
|                         ), | ||||
|                         _buildInfoItem( | ||||
|                           context, | ||||
|                           icon: Symbols.update, | ||||
|                           label: 'aboutScreenVersionLabel'.tr(), | ||||
|                           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'], | ||||
|                         const SizedBox(height: 24), | ||||
|                         // App Icon and Name | ||||
|                         CircleAvatar( | ||||
|                           radius: 50, | ||||
|                           backgroundColor: theme.colorScheme.primary | ||||
|                               .withOpacity(0.1), | ||||
|                           child: Image.asset( | ||||
|                             'assets/icons/icon.png', | ||||
|                             width: 56, | ||||
|                             height: 56, | ||||
|                           ), | ||||
|                           _buildInfoItem( | ||||
|                             context, | ||||
|                             icon: Symbols.fingerprint, | ||||
|                             label: 'aboutDeviceIdentifier'.tr(), | ||||
|                             value: _deviceUdid ?? 'N/A', | ||||
|                             copyable: true, | ||||
|                         ), | ||||
|                         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), | ||||
|  | ||||
|                     const SizedBox(height: 16), | ||||
|  | ||||
|                     // Links Card | ||||
|                     _buildSection( | ||||
|                       context, | ||||
|                       title: 'aboutScreenLinksSectionTitle'.tr(), | ||||
|                       children: [ | ||||
|                         _buildListTile( | ||||
|                         // App Info Card | ||||
|                         _buildSection( | ||||
|                           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.', | ||||
|                           title: 'aboutScreenAppInfoSectionTitle'.tr(), | ||||
|                           children: [ | ||||
|                             _buildInfoItem( | ||||
|                               context, | ||||
|                               icon: Symbols.info, | ||||
|                               label: 'aboutScreenPackageNameLabel'.tr(), | ||||
|                               value: _packageInfo.packageName, | ||||
|                             ), | ||||
|                             _buildInfoItem( | ||||
|                               context, | ||||
|                               icon: Symbols.update, | ||||
|                               label: 'aboutScreenVersionLabel'.tr(), | ||||
|                               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, | ||||
|                                 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 | ||||
|                                 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( | ||||
|                           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), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                             _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}', | ||||
|                                 ); | ||||
|                               }, | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|  | ||||
|                     Gap(MediaQuery.of(context).padding.bottom + 16), | ||||
|                   ], | ||||
|                         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:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:gap/gap.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/screens/notification.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/app_scaffold.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:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| @@ -276,30 +273,6 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                 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), | ||||
|             ListTile( | ||||
|               minTileHeight: 48, | ||||
| @@ -311,6 +284,19 @@ class AccountScreen extends HookConsumerWidget { | ||||
|                 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( | ||||
|               minTileHeight: 48, | ||||
|               leading: const Icon(Symbols.logout), | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import 'package:island/pods/network.dart'; | ||||
| import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/services/color.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/services/text.dart'; | ||||
| import 'package:island/services/time.dart'; | ||||
| import 'package:island/services/timezone/native.dart'; | ||||
| import 'package:island/widgets/account/account_name.dart'; | ||||
| @@ -30,6 +31,7 @@ import 'package:palette_generator/palette_generator.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:share_plus/share_plus.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
|  | ||||
| part 'profile.g.dart'; | ||||
|  | ||||
| @@ -350,6 +352,28 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|       ).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.entries) | ||||
|             ListTile( | ||||
|               title: Text(link.key.capitalizeEachWord()), | ||||
|               subtitle: Text(link.value), | ||||
|               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|               trailing: const Icon(Symbols.chevron_right), | ||||
|               shape: RoundedRectangleBorder( | ||||
|                 borderRadius: const BorderRadius.all(Radius.circular(8)), | ||||
|               ), | ||||
|               onTap: () { | ||||
|                 launchUrlString(link.value); | ||||
|               }, | ||||
|             ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     Widget accountAction(SnAccount data) => Card( | ||||
|       child: Column( | ||||
|         children: [ | ||||
| @@ -452,7 +476,7 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ).padding(horizontal: 16, vertical: 8), | ||||
|       ).padding(horizontal: 16, vertical: 12), | ||||
|     ); | ||||
|  | ||||
|     return account.when( | ||||
| @@ -537,6 +561,9 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: accountProfileBio(data).padding(top: 4), | ||||
|                               ), | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: accountProfileLinks(data), | ||||
|                               ), | ||||
|                               SliverToBoxAdapter( | ||||
|                                 child: accountProfileDetail(data), | ||||
|                               ), | ||||
| @@ -633,6 +660,11 @@ class AccountProfileScreen extends HookConsumerWidget { | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: accountProfileBio(data).padding(horizontal: 4), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: accountProfileLinks( | ||||
|                             data, | ||||
|                           ).padding(horizontal: 4), | ||||
|                         ), | ||||
|                         SliverToBoxAdapter( | ||||
|                           child: accountProfileDetail( | ||||
|                             data, | ||||
|   | ||||
| @@ -30,12 +30,12 @@ Future<DeveloperStats?> developerStats(Ref ref, String? uname) async { | ||||
| } | ||||
|  | ||||
| @riverpod | ||||
| Future<List<SnPublisher>> developers(Ref ref) async { | ||||
| Future<List<SnDeveloper>> developers(Ref ref) async { | ||||
|   final client = ref.watch(apiClientProvider); | ||||
|   final resp = await client.get('/develop/developers'); | ||||
|   return resp.data | ||||
|       .map((e) => SnPublisher.fromJson(e)) | ||||
|       .cast<SnPublisher>() | ||||
|       .map((e) => SnDeveloper.fromJson(e)) | ||||
|       .cast<SnDeveloper>() | ||||
|       .toList(); | ||||
| } | ||||
|  | ||||
| @@ -74,25 +74,25 @@ class DeveloperHubScreen extends HookConsumerWidget { | ||||
|     } | ||||
|  | ||||
|     final developers = ref.watch(developersProvider); | ||||
|     final currentDeveloper = useState<SnPublisher?>( | ||||
|     final currentDeveloper = useState<SnDeveloper?>( | ||||
|       developers.value?.firstOrNull, | ||||
|     ); | ||||
|  | ||||
|     final List<DropdownMenuItem<SnPublisher>> developersMenu = developers.when( | ||||
|     final List<DropdownMenuItem<SnDeveloper>> developersMenu = developers.when( | ||||
|       data: | ||||
|           (data) => | ||||
|               data | ||||
|                   .map( | ||||
|                     (item) => DropdownMenuItem<SnPublisher>( | ||||
|                     (item) => DropdownMenuItem<SnDeveloper>( | ||||
|                       value: item, | ||||
|                       child: ListTile( | ||||
|                         minTileHeight: 48, | ||||
|                         leading: ProfilePictureWidget( | ||||
|                           radius: 16, | ||||
|                           fileId: item.picture?.id, | ||||
|                           fileId: item.publisher?.picture?.id, | ||||
|                         ), | ||||
|                         title: Text(item.nick), | ||||
|                         subtitle: Text('@${item.name}'), | ||||
|                         title: Text(item.publisher!.nick), | ||||
|                         subtitle: Text('@${item.publisher!.name}'), | ||||
|                         trailing: | ||||
|                             currentDeveloper.value?.id == item.id | ||||
|                                 ? const Icon(Icons.check) | ||||
| @@ -107,7 +107,7 @@ class DeveloperHubScreen extends HookConsumerWidget { | ||||
|     ); | ||||
|  | ||||
|     final developerStats = ref.watch( | ||||
|       developerStatsProvider(currentDeveloper.value?.name), | ||||
|       developerStatsProvider(currentDeveloper.value?.publisher?.name), | ||||
|     ); | ||||
|  | ||||
|     return AppScaffold( | ||||
| @@ -117,7 +117,7 @@ class DeveloperHubScreen extends HookConsumerWidget { | ||||
|         title: Text('developerHub').tr(), | ||||
|         actions: [ | ||||
|           DropdownButtonHideUnderline( | ||||
|             child: DropdownButton2<SnPublisher>( | ||||
|             child: DropdownButton2<SnDeveloper>( | ||||
|               alignment: Alignment.centerRight, | ||||
|               value: currentDeveloper.value, | ||||
|               hint: CircleAvatar( | ||||
| @@ -139,7 +139,7 @@ class DeveloperHubScreen extends HookConsumerWidget { | ||||
|                   ...developersMenu.map( | ||||
|                     (e) => ProfilePictureWidget( | ||||
|                       radius: 16, | ||||
|                       fileId: e.value?.picture?.id, | ||||
|                       fileId: e.value?.publisher?.picture?.id, | ||||
|                     ).center().padding(right: 8), | ||||
|                   ), | ||||
|                 ]; | ||||
| @@ -193,10 +193,12 @@ class DeveloperHubScreen extends HookConsumerWidget { | ||||
|                           ...(developers.value?.map( | ||||
|                                 (developer) => ListTile( | ||||
|                                   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: () { | ||||
|                                     currentDeveloper.value = developer; | ||||
|                                   }, | ||||
| @@ -243,7 +245,8 @@ class DeveloperHubScreen extends HookConsumerWidget { | ||||
|                               context.pushNamed( | ||||
|                                 'developerApps', | ||||
|                                 pathParameters: { | ||||
|                                   'name': currentDeveloper.value!.name, | ||||
|                                   'name': | ||||
|                                       currentDeveloper.value!.publisher!.name, | ||||
|                                 }, | ||||
|                               ); | ||||
|                             }, | ||||
| @@ -257,7 +260,9 @@ class DeveloperHubScreen extends HookConsumerWidget { | ||||
|               error: err, | ||||
|               onRetry: () { | ||||
|                 ref.invalidate( | ||||
|                   developerStatsProvider(currentDeveloper.value?.name), | ||||
|                   developerStatsProvider( | ||||
|                     currentDeveloper.value?.publisher!.name, | ||||
|                   ), | ||||
|                 ); | ||||
|               }, | ||||
|             ), | ||||
| @@ -354,7 +359,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget { | ||||
|                     ? Center( | ||||
|                       child: | ||||
|                           Text( | ||||
|                             'noPublishersToEnroll', | ||||
|                             'noDevelopersToEnroll', | ||||
|                             textAlign: TextAlign.center, | ||||
|                           ).tr(), | ||||
|                     ) | ||||
|   | ||||
| @@ -149,12 +149,12 @@ class _DeveloperStatsProviderElement | ||||
|   String? get uname => (origin as DeveloperStatsProvider).uname; | ||||
| } | ||||
|  | ||||
| String _$developersHash() => r'04f25db31f511f651a5add128d56631236ed0b39'; | ||||
| String _$developersHash() => r'252341098617ac398ce133994453f318dd3edbd2'; | ||||
|  | ||||
| /// See also [developers]. | ||||
| @ProviderFor(developers) | ||||
| final developersProvider = | ||||
|     AutoDisposeFutureProvider<List<SnPublisher>>.internal( | ||||
|     AutoDisposeFutureProvider<List<SnDeveloper>>.internal( | ||||
|       developers, | ||||
|       name: r'developersProvider', | ||||
|       debugGetCreateSourceHash: | ||||
| @@ -167,6 +167,6 @@ final developersProvider = | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnPublisher>>; | ||||
| typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnDeveloper>>; | ||||
| // 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 | ||||
|   | ||||
| @@ -331,7 +331,7 @@ class _WebSocketIndicator extends HookConsumerWidget { | ||||
|     final user = ref.watch(userInfoProvider); | ||||
|     final websocketState = ref.watch(websocketStateProvider); | ||||
|     final indicatorHeight = | ||||
|         MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 20); | ||||
|         MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 25); | ||||
|  | ||||
|     Color indicatorColor; | ||||
|     String indicatorText; | ||||
|   | ||||
| @@ -142,7 +142,7 @@ class CloudVideoWidget extends HookConsumerWidget { | ||||
|               mainAxisAlignment: MainAxisAlignment.end, | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 Row( | ||||
|                 Wrap( | ||||
|                   spacing: 8, | ||||
|                   children: [ | ||||
|                     if (item.fileMeta?['duration'] != null) | ||||
| @@ -199,8 +199,8 @@ class CloudVideoWidget extends HookConsumerWidget { | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ).padding(horizontal: 16, bottom: 12), | ||||
|             ).padding(horizontal: 16, bottom: 12), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|       onTap: () { | ||||
|   | ||||
							
								
								
									
										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(); | ||||
|             }, | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										72
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -73,22 +73,6 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -205,10 +189,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: built_value | ||||
|       sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62" | ||||
|       sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.11.0" | ||||
|     version: "8.11.1" | ||||
|   cached_network_image: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -453,10 +437,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: dio | ||||
|       sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" | ||||
|       sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.8.0+1" | ||||
|     version: "5.9.0" | ||||
|   dio_web_adapter: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -573,10 +557,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: file_picker | ||||
|       sha256: "13ba4e627ef24503a465d1d61b32596ce10eb6b8903678d362a528f9939b4aa8" | ||||
|       sha256: "8f9f429998f9232d65bc4757af74475ce44fc80f10704ff5dfa8b1d14fc429b9" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.2.1" | ||||
|     version: "10.2.3" | ||||
|   file_selector_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -911,10 +895,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_plugin_android_lifecycle | ||||
|       sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e | ||||
|       sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.28" | ||||
|     version: "2.0.29" | ||||
|   flutter_popup_card: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1033,10 +1017,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: font_awesome_flutter | ||||
|       sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a | ||||
|       sha256: f50ce90dbe26d977415b9540400d6778bef00894aced6358ae578abd92b14b10 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.8.0" | ||||
|     version: "10.9.0" | ||||
|   freezed: | ||||
|     dependency: "direct dev" | ||||
|     description: | ||||
| @@ -1089,10 +1073,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: go_router | ||||
|       sha256: c489908a54ce2131f1d1b7cc631af9c1a06fac5ca7c449e959192089f9489431 | ||||
|       sha256: "8b1f37dfaf6e958c6b872322db06f946509433bec3de753c3491a42ae9ec2b48" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "16.0.0" | ||||
|     version: "16.1.0" | ||||
|   google_fonts: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1153,10 +1137,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: http | ||||
|       sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" | ||||
|       sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.4.0" | ||||
|     version: "1.5.0" | ||||
|   http_multi_server: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1193,10 +1177,10 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: image_picker_android | ||||
|       sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d" | ||||
|       sha256: b08e9a04d0f8d91f4a6e767a745b9871bfbc585410205c311d0492de20a7ccd6 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.8.12+24" | ||||
|     version: "0.8.12+25" | ||||
|   image_picker_for_web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1361,18 +1345,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: local_auth_android | ||||
|       sha256: "82b2bdeee2199a510d3b7716121e96a6609da86693bb0863edd8566355406b79" | ||||
|       sha256: "316503f6772dea9c0c038bb7aac4f68ab00112d707d258c770f7fc3c250a2d88" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.50" | ||||
|     version: "1.0.51" | ||||
|   local_auth_darwin: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: local_auth_darwin | ||||
|       sha256: "25163ce60a5a6c468cf7a0e3dc8a165f824cabc2aa9e39a5e9fc5c2311b7686f" | ||||
|       sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.5.0" | ||||
|     version: "1.6.0" | ||||
|   local_auth_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2033,10 +2017,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: shared_preferences_android | ||||
|       sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" | ||||
|       sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.4.10" | ||||
|     version: "2.4.11" | ||||
|   shared_preferences_foundation: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2206,18 +2190,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: sqlite3 | ||||
|       sha256: dd806fff004a0aeb01e208b858dbc649bc72104670d425a81a6dd17698535f6e | ||||
|       sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.8.0" | ||||
|     version: "2.9.0" | ||||
|   sqlite3_flutter_libs: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: sqlite3_flutter_libs | ||||
|       sha256: fd996da5515a73aacd0a04ae7063db5fe8df42670d974df4c3ee538c652eef2e | ||||
|       sha256: "2b03273e71867a8a4d030861fc21706200debe5c5858a4b9e58f4a1c129586a4" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.5.38" | ||||
|     version: "0.5.39" | ||||
|   sqlparser: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -2424,10 +2408,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: url_launcher_android | ||||
|       sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" | ||||
|       sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.3.16" | ||||
|     version: "6.3.17" | ||||
|   url_launcher_ios: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
							
								
								
									
										11
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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 | ||||
| # 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. | ||||
| version: 3.1.0+117 | ||||
| version: 3.1.0+120 | ||||
|  | ||||
| environment: | ||||
|   sdk: ^3.7.2 | ||||
| @@ -39,12 +39,12 @@ dependencies: | ||||
|   flutter_hooks: ^0.21.2 | ||||
|   hooks_riverpod: ^2.6.1 | ||||
|   bitsdojo_window: ^0.1.6 | ||||
|   go_router: ^16.0.0 | ||||
|   go_router: ^16.1.0 | ||||
|   styled_widget: ^0.4.1 | ||||
|   shared_preferences: ^2.5.3 | ||||
|   flutter_riverpod: ^2.6.1 | ||||
|   path_provider: ^2.1.5 | ||||
|   dio: ^5.8.0+1 | ||||
|   dio: ^5.9.0 | ||||
|   very_good_infinite_list: ^0.9.0 | ||||
|   freezed_annotation: ^3.1.0 | ||||
|   json_annotation: ^4.9.0 | ||||
| @@ -73,10 +73,10 @@ dependencies: | ||||
|     git: https://github.com/LittleSheep2Code/tus_client.git | ||||
|   cross_file: ^0.3.4+2 | ||||
|   image_picker: ^1.1.2 | ||||
|   file_picker: ^10.2.1 | ||||
|   file_picker: ^10.2.3 | ||||
|   riverpod_annotation: ^2.6.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 | ||||
|   modal_bottom_sheet: ^3.0.0 | ||||
|   firebase_messaging: ^16.0.0 | ||||
| @@ -144,7 +144,6 @@ dev_dependencies: | ||||
|   # package. See that file for information about deactivating specific lint | ||||
|   # rules and activating additional ones. | ||||
|   flutter_lints: ^6.0.0 | ||||
|   auto_route_generator: ^10.1.0 | ||||
|   build_runner: ^2.5.4 | ||||
|   freezed: ^3.1.0 | ||||
|   json_serializable: ^6.9.5 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user