Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
808e7dcffa | ||
|
9bed4fa6fb | ||
|
e6255a340b | ||
|
78bf319fb7 | ||
|
36a966d582 | ||
|
f72b268d36 | ||
|
44ef31034e | ||
|
229dc2186f | ||
|
a2f9a1efb4 | ||
|
823e3c5de6 | ||
|
faac7bac35 | ||
|
1fac1bfe02 | ||
|
9394b1d9c8 | ||
|
43dd13bac4 | ||
|
65bc372103 | ||
|
6558854a7a | ||
|
892035ab27 |
@@ -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",
|
||||||
@@ -787,5 +788,6 @@
|
|||||||
"addLink": "Add link",
|
"addLink": "Add link",
|
||||||
"linkKey": "Link Name",
|
"linkKey": "Link Name",
|
||||||
"linkValue": "URL",
|
"linkValue": "URL",
|
||||||
"debugOptions": "Debug Options"
|
"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 {
|
||||||
|
@@ -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';
|
||||||
@@ -205,33 +205,16 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
// Fetch latest release and show the unified sheet
|
// Fetch latest release and show the unified sheet
|
||||||
final svc = UpdateService();
|
final svc = UpdateService();
|
||||||
// Reuse service fetch + compare to decide content
|
// Reuse service fetch + compare to decide content
|
||||||
|
showLoadingModal(context);
|
||||||
final release = await svc.fetchLatestRelease();
|
final release = await svc.fetchLatestRelease();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
hideLoadingModal(context);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
await svc.showUpdateSheet(context, release);
|
await svc.showUpdateSheet(context, release);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: show a simple sheet indicating no info
|
showInfoAlert(
|
||||||
// Use your SheetScaffold for consistent styling
|
'Currently cannot get update from the GitHub.',
|
||||||
// Show a minimal message
|
'Unable to check for updates',
|
||||||
// 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.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -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';
|
||||||
@@ -196,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,
|
||||||
@@ -322,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: [
|
||||||
@@ -357,17 +367,21 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||||
for (final link in data.profile.links.entries)
|
for (final link in data.profile.links)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(link.key.capitalizeEachWord()),
|
title: Text(link.name.capitalizeEachWord()),
|
||||||
subtitle: Text(link.value),
|
subtitle: Text(link.url),
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
launchUrlString(link.value);
|
if (!link.url.startsWith('http') && !link.url.contains('://')) {
|
||||||
|
launchUrlString('https://${link.url}');
|
||||||
|
} else {
|
||||||
|
launchUrlString(link.url);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -561,9 +575,10 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileBio(data).padding(top: 4),
|
child: accountProfileBio(data).padding(top: 4),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
if (data.profile.links.isNotEmpty)
|
||||||
child: accountProfileLinks(data),
|
SliverToBoxAdapter(
|
||||||
),
|
child: accountProfileLinks(data),
|
||||||
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileDetail(data),
|
child: accountProfileDetail(data),
|
||||||
),
|
),
|
||||||
@@ -660,11 +675,12 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: accountProfileBio(data).padding(horizontal: 4),
|
child: accountProfileBio(data).padding(horizontal: 4),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
if (data.profile.links.isNotEmpty)
|
||||||
child: accountProfileLinks(
|
SliverToBoxAdapter(
|
||||||
data,
|
child: accountProfileLinks(
|
||||||
).padding(horizontal: 4),
|
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')),
|
||||||
),
|
),
|
||||||
|
@@ -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'),
|
||||||
|
@@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -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+119
|
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