Compare commits
25 Commits
3.2.0+132
...
c7f9da0dee
| Author | SHA1 | Date | |
|---|---|---|---|
|
c7f9da0dee
|
|||
|
|
a243cda1df | ||
|
|
7b238f32fd | ||
|
313af28d7f
|
|||
|
c64e1e208c
|
|||
|
c9b07a9a2a
|
|||
| 55c0e355f1 | |||
| be414891ec | |||
| 787876ab6a | |||
|
8578cde620
|
|||
|
14d55d45a8
|
|||
|
724391584e
|
|||
| 3a5e45808a | |||
|
488055955c
|
|||
|
|
313ebc64cc | ||
|
|
1ed8b1d0c1 | ||
| 4af816d931 | |||
| 1c058a4323 | |||
| 461ed1fcda | |||
|
5363afa558
|
|||
|
f0d2737da8
|
|||
|
1f2f80aa3e
|
|||
|
240a872e65
|
|||
| c1ec6f0849 | |||
| ab42686d4d |
@@ -181,7 +181,7 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (!kIsWeb && Platform.isLinux) {
|
if (!kIsWeb && (Platform.isLinux || Platform.isWindows)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ sealed class AppToken with _$AppToken {
|
|||||||
@freezed
|
@freezed
|
||||||
sealed class GeoIpLocation with _$GeoIpLocation {
|
sealed class GeoIpLocation with _$GeoIpLocation {
|
||||||
const factory GeoIpLocation({
|
const factory GeoIpLocation({
|
||||||
required double latitude,
|
required double? latitude,
|
||||||
required double longitude,
|
required double? longitude,
|
||||||
required String countryCode,
|
required String? countryCode,
|
||||||
required String country,
|
required String? country,
|
||||||
required String city,
|
required String? city,
|
||||||
}) = _GeoIpLocation;
|
}) = _GeoIpLocation;
|
||||||
|
|
||||||
factory GeoIpLocation.fromJson(Map<String, dynamic> json) =>
|
factory GeoIpLocation.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -29,7 +29,7 @@ sealed class GeoIpLocation with _$GeoIpLocation {
|
|||||||
sealed class SnAuthChallenge with _$SnAuthChallenge {
|
sealed class SnAuthChallenge with _$SnAuthChallenge {
|
||||||
const factory SnAuthChallenge({
|
const factory SnAuthChallenge({
|
||||||
required String id,
|
required String id,
|
||||||
required DateTime expiredAt,
|
required DateTime? expiredAt,
|
||||||
required int stepRemain,
|
required int stepRemain,
|
||||||
required int stepTotal,
|
required int stepTotal,
|
||||||
required int failedAttempts,
|
required int failedAttempts,
|
||||||
@@ -57,7 +57,7 @@ sealed class SnAuthSession with _$SnAuthSession {
|
|||||||
required String id,
|
required String id,
|
||||||
required String? label,
|
required String? label,
|
||||||
required DateTime lastGrantedAt,
|
required DateTime lastGrantedAt,
|
||||||
required DateTime expiredAt,
|
required DateTime? expiredAt,
|
||||||
required String accountId,
|
required String accountId,
|
||||||
required String challengeId,
|
required String challengeId,
|
||||||
required SnAuthChallenge challenge,
|
required SnAuthChallenge challenge,
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ as String,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$GeoIpLocation {
|
mixin _$GeoIpLocation {
|
||||||
|
|
||||||
double get latitude; double get longitude; String get countryCode; String get country; String get city;
|
double? get latitude; double? get longitude; String? get countryCode; String? get country; String? get city;
|
||||||
/// Create a copy of GeoIpLocation
|
/// Create a copy of GeoIpLocation
|
||||||
/// 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)
|
||||||
@@ -305,7 +305,7 @@ abstract mixin class $GeoIpLocationCopyWith<$Res> {
|
|||||||
factory $GeoIpLocationCopyWith(GeoIpLocation value, $Res Function(GeoIpLocation) _then) = _$GeoIpLocationCopyWithImpl;
|
factory $GeoIpLocationCopyWith(GeoIpLocation value, $Res Function(GeoIpLocation) _then) = _$GeoIpLocationCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
double latitude, double longitude, String countryCode, String country, String city
|
double? latitude, double? longitude, String? countryCode, String? country, String? city
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -322,14 +322,14 @@ class _$GeoIpLocationCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of GeoIpLocation
|
/// Create a copy of GeoIpLocation
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? latitude = null,Object? longitude = null,Object? countryCode = null,Object? country = null,Object? city = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? latitude = freezed,Object? longitude = freezed,Object? countryCode = freezed,Object? country = freezed,Object? city = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
latitude: null == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
|
latitude: freezed == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
|
||||||
as double,longitude: null == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
|
as double?,longitude: freezed == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
|
||||||
as double,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
as double?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
||||||
as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
||||||
as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
|
as String?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +411,7 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( double latitude, double longitude, String countryCode, String country, String city)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( double? latitude, double? longitude, String? countryCode, String? country, String? city)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _GeoIpLocation() when $default != null:
|
case _GeoIpLocation() when $default != null:
|
||||||
return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);case _:
|
return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);case _:
|
||||||
@@ -432,7 +432,7 @@ return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( double latitude, double longitude, String countryCode, String country, String city) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( double? latitude, double? longitude, String? countryCode, String? country, String? city) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _GeoIpLocation():
|
case _GeoIpLocation():
|
||||||
return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);}
|
return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);}
|
||||||
@@ -449,7 +449,7 @@ return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( double latitude, double longitude, String countryCode, String country, String city)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( double? latitude, double? longitude, String? countryCode, String? country, String? city)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _GeoIpLocation() when $default != null:
|
case _GeoIpLocation() when $default != null:
|
||||||
return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);case _:
|
return $default(_that.latitude,_that.longitude,_that.countryCode,_that.country,_that.city);case _:
|
||||||
@@ -467,11 +467,11 @@ class _GeoIpLocation implements GeoIpLocation {
|
|||||||
const _GeoIpLocation({required this.latitude, required this.longitude, required this.countryCode, required this.country, required this.city});
|
const _GeoIpLocation({required this.latitude, required this.longitude, required this.countryCode, required this.country, required this.city});
|
||||||
factory _GeoIpLocation.fromJson(Map<String, dynamic> json) => _$GeoIpLocationFromJson(json);
|
factory _GeoIpLocation.fromJson(Map<String, dynamic> json) => _$GeoIpLocationFromJson(json);
|
||||||
|
|
||||||
@override final double latitude;
|
@override final double? latitude;
|
||||||
@override final double longitude;
|
@override final double? longitude;
|
||||||
@override final String countryCode;
|
@override final String? countryCode;
|
||||||
@override final String country;
|
@override final String? country;
|
||||||
@override final String city;
|
@override final String? city;
|
||||||
|
|
||||||
/// Create a copy of GeoIpLocation
|
/// Create a copy of GeoIpLocation
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -506,7 +506,7 @@ abstract mixin class _$GeoIpLocationCopyWith<$Res> implements $GeoIpLocationCopy
|
|||||||
factory _$GeoIpLocationCopyWith(_GeoIpLocation value, $Res Function(_GeoIpLocation) _then) = __$GeoIpLocationCopyWithImpl;
|
factory _$GeoIpLocationCopyWith(_GeoIpLocation value, $Res Function(_GeoIpLocation) _then) = __$GeoIpLocationCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
double latitude, double longitude, String countryCode, String country, String city
|
double? latitude, double? longitude, String? countryCode, String? country, String? city
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -523,14 +523,14 @@ class __$GeoIpLocationCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of GeoIpLocation
|
/// Create a copy of GeoIpLocation
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? latitude = null,Object? longitude = null,Object? countryCode = null,Object? country = null,Object? city = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? latitude = freezed,Object? longitude = freezed,Object? countryCode = freezed,Object? country = freezed,Object? city = freezed,}) {
|
||||||
return _then(_GeoIpLocation(
|
return _then(_GeoIpLocation(
|
||||||
latitude: null == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
|
latitude: freezed == latitude ? _self.latitude : latitude // ignore: cast_nullable_to_non_nullable
|
||||||
as double,longitude: null == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
|
as double?,longitude: freezed == longitude ? _self.longitude : longitude // ignore: cast_nullable_to_non_nullable
|
||||||
as double,countryCode: null == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
as double?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
|
||||||
as String,country: null == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
as String?,country: freezed == country ? _self.country : country // ignore: cast_nullable_to_non_nullable
|
||||||
as String,city: null == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
|
as String?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +541,7 @@ as String,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnAuthChallenge {
|
mixin _$SnAuthChallenge {
|
||||||
|
|
||||||
String get id; DateTime get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String? get nonce; GeoIpLocation? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
String get id; DateTime? get expiredAt; int get stepRemain; int get stepTotal; int get failedAttempts; int get type; List<String> get blacklistFactors; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; String? get nonce; GeoIpLocation? get location; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
/// Create a copy of SnAuthChallenge
|
/// Create a copy of SnAuthChallenge
|
||||||
/// 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)
|
||||||
@@ -574,7 +574,7 @@ abstract mixin class $SnAuthChallengeCopyWith<$Res> {
|
|||||||
factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl;
|
factory $SnAuthChallengeCopyWith(SnAuthChallenge value, $Res Function(SnAuthChallenge) _then) = _$SnAuthChallengeCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, DateTime? expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -591,11 +591,11 @@ class _$SnAuthChallengeCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthChallenge
|
/// Create a copy of SnAuthChallenge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? expiredAt = freezed,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
as String,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
as DateTime?,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
||||||
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
|
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
|
||||||
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -704,7 +704,7 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, DateTime? expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnAuthChallenge() when $default != null:
|
case _SnAuthChallenge() when $default != null:
|
||||||
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||||
@@ -725,7 +725,7 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, DateTime? expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnAuthChallenge():
|
case _SnAuthChallenge():
|
||||||
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||||
@@ -742,7 +742,7 @@ return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, DateTime? expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnAuthChallenge() when $default != null:
|
case _SnAuthChallenge() when $default != null:
|
||||||
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
return $default(_that.id,_that.expiredAt,_that.stepRemain,_that.stepTotal,_that.failedAttempts,_that.type,_that.blacklistFactors,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.nonce,_that.location,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||||
@@ -761,7 +761,7 @@ class _SnAuthChallenge implements SnAuthChallenge {
|
|||||||
factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json);
|
factory _SnAuthChallenge.fromJson(Map<String, dynamic> json) => _$SnAuthChallengeFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final DateTime expiredAt;
|
@override final DateTime? expiredAt;
|
||||||
@override final int stepRemain;
|
@override final int stepRemain;
|
||||||
@override final int stepTotal;
|
@override final int stepTotal;
|
||||||
@override final int failedAttempts;
|
@override final int failedAttempts;
|
||||||
@@ -829,7 +829,7 @@ abstract mixin class _$SnAuthChallengeCopyWith<$Res> implements $SnAuthChallenge
|
|||||||
factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl;
|
factory _$SnAuthChallengeCopyWith(_SnAuthChallenge value, $Res Function(_SnAuthChallenge) _then) = __$SnAuthChallengeCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, DateTime expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, DateTime? expiredAt, int stepRemain, int stepTotal, int failedAttempts, int type, List<String> blacklistFactors, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, String? nonce, GeoIpLocation? location, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -846,11 +846,11 @@ class __$SnAuthChallengeCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthChallenge
|
/// Create a copy of SnAuthChallenge
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = null,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? expiredAt = freezed,Object? stepRemain = null,Object? stepTotal = null,Object? failedAttempts = null,Object? type = null,Object? blacklistFactors = null,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? nonce = freezed,Object? location = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_SnAuthChallenge(
|
return _then(_SnAuthChallenge(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
as String,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
as DateTime?,stepRemain: null == stepRemain ? _self.stepRemain : stepRemain // ignore: cast_nullable_to_non_nullable
|
||||||
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
as int,stepTotal: null == stepTotal ? _self.stepTotal : stepTotal // ignore: cast_nullable_to_non_nullable
|
||||||
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
|
as int,failedAttempts: null == failedAttempts ? _self.failedAttempts : failedAttempts // ignore: cast_nullable_to_non_nullable
|
||||||
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as int,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -888,7 +888,7 @@ $GeoIpLocationCopyWith<$Res>? get location {
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnAuthSession {
|
mixin _$SnAuthSession {
|
||||||
|
|
||||||
String get id; String? get label; DateTime get lastGrantedAt; DateTime get expiredAt; String get accountId; String get challengeId; SnAuthChallenge get challenge; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
String get id; String? get label; DateTime get lastGrantedAt; DateTime? get expiredAt; String get accountId; String get challengeId; SnAuthChallenge get challenge; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
/// Create a copy of SnAuthSession
|
/// Create a copy of SnAuthSession
|
||||||
/// 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)
|
||||||
@@ -921,7 +921,7 @@ abstract mixin class $SnAuthSessionCopyWith<$Res> {
|
|||||||
factory $SnAuthSessionCopyWith(SnAuthSession value, $Res Function(SnAuthSession) _then) = _$SnAuthSessionCopyWithImpl;
|
factory $SnAuthSessionCopyWith(SnAuthSession value, $Res Function(SnAuthSession) _then) = _$SnAuthSessionCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -938,13 +938,13 @@ class _$SnAuthSessionCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthSession
|
/// Create a copy of SnAuthSession
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = freezed,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
|
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
|
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
|
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
|
||||||
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -1041,7 +1041,7 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnAuthSession() when $default != null:
|
case _SnAuthSession() when $default != null:
|
||||||
return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.accountId,_that.challengeId,_that.challenge,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.accountId,_that.challengeId,_that.challenge,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||||
@@ -1062,7 +1062,7 @@ return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.a
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnAuthSession():
|
case _SnAuthSession():
|
||||||
return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.accountId,_that.challengeId,_that.challenge,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.accountId,_that.challengeId,_that.challenge,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||||
@@ -1079,7 +1079,7 @@ return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.a
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnAuthSession() when $default != null:
|
case _SnAuthSession() when $default != null:
|
||||||
return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.accountId,_that.challengeId,_that.challenge,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.accountId,_that.challengeId,_that.challenge,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||||
@@ -1100,7 +1100,7 @@ class _SnAuthSession implements SnAuthSession {
|
|||||||
@override final String id;
|
@override final String id;
|
||||||
@override final String? label;
|
@override final String? label;
|
||||||
@override final DateTime lastGrantedAt;
|
@override final DateTime lastGrantedAt;
|
||||||
@override final DateTime expiredAt;
|
@override final DateTime? expiredAt;
|
||||||
@override final String accountId;
|
@override final String accountId;
|
||||||
@override final String challengeId;
|
@override final String challengeId;
|
||||||
@override final SnAuthChallenge challenge;
|
@override final SnAuthChallenge challenge;
|
||||||
@@ -1141,7 +1141,7 @@ abstract mixin class _$SnAuthSessionCopyWith<$Res> implements $SnAuthSessionCopy
|
|||||||
factory _$SnAuthSessionCopyWith(_SnAuthSession value, $Res Function(_SnAuthSession) _then) = __$SnAuthSessionCopyWithImpl;
|
factory _$SnAuthSessionCopyWith(_SnAuthSession value, $Res Function(_SnAuthSession) _then) = __$SnAuthSessionCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? label, DateTime lastGrantedAt, DateTime expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, String accountId, String challengeId, SnAuthChallenge challenge, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1158,13 +1158,13 @@ class __$SnAuthSessionCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnAuthSession
|
/// Create a copy of SnAuthSession
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = null,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = freezed,Object? lastGrantedAt = null,Object? expiredAt = freezed,Object? accountId = null,Object? challengeId = null,Object? challenge = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
return _then(_SnAuthSession(
|
return _then(_SnAuthSession(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
as String,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
|
as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGrantedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
as DateTime,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
|
as String,challengeId: null == challengeId ? _self.challengeId : challengeId // ignore: cast_nullable_to_non_nullable
|
||||||
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
|
as String,challenge: null == challenge ? _self.challenge : challenge // ignore: cast_nullable_to_non_nullable
|
||||||
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
as SnAuthChallenge,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ Map<String, dynamic> _$AppTokenToJson(_AppToken instance) => <String, dynamic>{
|
|||||||
|
|
||||||
_GeoIpLocation _$GeoIpLocationFromJson(Map<String, dynamic> json) =>
|
_GeoIpLocation _$GeoIpLocationFromJson(Map<String, dynamic> json) =>
|
||||||
_GeoIpLocation(
|
_GeoIpLocation(
|
||||||
latitude: (json['latitude'] as num).toDouble(),
|
latitude: (json['latitude'] as num?)?.toDouble(),
|
||||||
longitude: (json['longitude'] as num).toDouble(),
|
longitude: (json['longitude'] as num?)?.toDouble(),
|
||||||
countryCode: json['country_code'] as String,
|
countryCode: json['country_code'] as String?,
|
||||||
country: json['country'] as String,
|
country: json['country'] as String?,
|
||||||
city: json['city'] as String,
|
city: json['city'] as String?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$GeoIpLocationToJson(_GeoIpLocation instance) =>
|
Map<String, dynamic> _$GeoIpLocationToJson(_GeoIpLocation instance) =>
|
||||||
@@ -34,7 +34,10 @@ Map<String, dynamic> _$GeoIpLocationToJson(_GeoIpLocation instance) =>
|
|||||||
_SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
|
_SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
|
||||||
_SnAuthChallenge(
|
_SnAuthChallenge(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
expiredAt: DateTime.parse(json['expired_at'] as String),
|
expiredAt:
|
||||||
|
json['expired_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['expired_at'] as String),
|
||||||
stepRemain: (json['step_remain'] as num).toInt(),
|
stepRemain: (json['step_remain'] as num).toInt(),
|
||||||
stepTotal: (json['step_total'] as num).toInt(),
|
stepTotal: (json['step_total'] as num).toInt(),
|
||||||
failedAttempts: (json['failed_attempts'] as num).toInt(),
|
failedAttempts: (json['failed_attempts'] as num).toInt(),
|
||||||
@@ -66,7 +69,7 @@ _SnAuthChallenge _$SnAuthChallengeFromJson(Map<String, dynamic> json) =>
|
|||||||
Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
|
Map<String, dynamic> _$SnAuthChallengeToJson(_SnAuthChallenge instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'expired_at': instance.expiredAt.toIso8601String(),
|
'expired_at': instance.expiredAt?.toIso8601String(),
|
||||||
'step_remain': instance.stepRemain,
|
'step_remain': instance.stepRemain,
|
||||||
'step_total': instance.stepTotal,
|
'step_total': instance.stepTotal,
|
||||||
'failed_attempts': instance.failedAttempts,
|
'failed_attempts': instance.failedAttempts,
|
||||||
@@ -89,7 +92,10 @@ _SnAuthSession _$SnAuthSessionFromJson(Map<String, dynamic> json) =>
|
|||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
label: json['label'] as String?,
|
label: json['label'] as String?,
|
||||||
lastGrantedAt: DateTime.parse(json['last_granted_at'] as String),
|
lastGrantedAt: DateTime.parse(json['last_granted_at'] as String),
|
||||||
expiredAt: DateTime.parse(json['expired_at'] as String),
|
expiredAt:
|
||||||
|
json['expired_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['expired_at'] as String),
|
||||||
accountId: json['account_id'] as String,
|
accountId: json['account_id'] as String,
|
||||||
challengeId: json['challenge_id'] as String,
|
challengeId: json['challenge_id'] as String,
|
||||||
challenge: SnAuthChallenge.fromJson(
|
challenge: SnAuthChallenge.fromJson(
|
||||||
@@ -108,7 +114,7 @@ Map<String, dynamic> _$SnAuthSessionToJson(_SnAuthSession instance) =>
|
|||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'label': instance.label,
|
'label': instance.label,
|
||||||
'last_granted_at': instance.lastGrantedAt.toIso8601String(),
|
'last_granted_at': instance.lastGrantedAt.toIso8601String(),
|
||||||
'expired_at': instance.expiredAt.toIso8601String(),
|
'expired_at': instance.expiredAt?.toIso8601String(),
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
'challenge_id': instance.challengeId,
|
'challenge_id': instance.challengeId,
|
||||||
'challenge': instance.challenge.toJson(),
|
'challenge': instance.challenge.toJson(),
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import 'package:shelf/shelf.dart';
|
|||||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||||
import 'package:shelf_web_socket/shelf_web_socket.dart';
|
import 'package:shelf_web_socket/shelf_web_socket.dart';
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
|
// Conditional imports for IPC server - use web stubs on web platform
|
||||||
|
import 'ipc_server.dart' if (dart.library.html) 'ipc_server.web.dart';
|
||||||
|
|
||||||
const String kRpcLogPrefix = 'arRPC.websocket';
|
const String kRpcLogPrefix = 'arRPC.websocket';
|
||||||
const String kRpcIpcLogPrefix = 'arRPC.ipc';
|
const String kRpcIpcLogPrefix = 'arRPC.ipc';
|
||||||
@@ -43,14 +45,14 @@ class IpcErrorCodes {
|
|||||||
static const int invalidEncoding = 4005;
|
static const int invalidEncoding = 4005;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reference https://github.com/OpenAsar/arrpc/blob/main/src/transports/ipc.js
|
||||||
class ActivityRpcServer {
|
class ActivityRpcServer {
|
||||||
static const List<int> portRange = [6463, 6472]; // Ports 6463–6472
|
static const List<int> portRange = [6463, 6472]; // Ports 6463–6472
|
||||||
Map<String, Function>
|
Map<String, Function>
|
||||||
handlers; // {connection: (socket), message: (socket, data), close: (socket)}
|
handlers; // {connection: (socket), message: (socket, data), close: (socket)}
|
||||||
HttpServer? _httpServer;
|
HttpServer? _httpServer;
|
||||||
ServerSocket? _ipcServer;
|
IpcServer? _ipcServer;
|
||||||
final List<WebSocketChannel> _wsSockets = [];
|
final List<WebSocketChannel> _wsSockets = [];
|
||||||
final List<_IpcSocketWrapper> _ipcSockets = [];
|
|
||||||
|
|
||||||
ActivityRpcServer(this.handlers);
|
ActivityRpcServer(this.handlers);
|
||||||
|
|
||||||
@@ -58,109 +60,20 @@ class ActivityRpcServer {
|
|||||||
handlers = newHandlers;
|
handlers = newHandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode IPC packet
|
// Start the server
|
||||||
static Uint8List encodeIpcPacket(int type, Map<String, dynamic> data) {
|
|
||||||
final jsonData = jsonEncode(data);
|
|
||||||
final dataBytes = utf8.encode(jsonData);
|
|
||||||
final dataSize = dataBytes.length;
|
|
||||||
|
|
||||||
final buffer = ByteData(8 + dataSize);
|
|
||||||
buffer.setInt32(0, type, Endian.little);
|
|
||||||
buffer.setInt32(4, dataSize, Endian.little);
|
|
||||||
buffer.buffer.asUint8List().setRange(8, 8 + dataSize, dataBytes);
|
|
||||||
|
|
||||||
return buffer.buffer.asUint8List();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _getMacOsSystemTmpDir() async {
|
|
||||||
final result = await Process.run('getconf', ['DARWIN_USER_TEMP_DIR']);
|
|
||||||
return (result.stdout as String).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find available IPC socket path
|
|
||||||
Future<String> _findAvailableIpcPath() async {
|
|
||||||
// Build list of directories to try, with macOS-specific handling
|
|
||||||
final baseDirs = <String>[];
|
|
||||||
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
try {
|
|
||||||
final macTempDir = await _getMacOsSystemTmpDir();
|
|
||||||
if (macTempDir.isNotEmpty) {
|
|
||||||
baseDirs.add(macTempDir);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
developer.log(
|
|
||||||
'Failed to get macOS system temp dir: $e',
|
|
||||||
name: kRpcIpcLogPrefix,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add other standard directories
|
|
||||||
final otherDirs = [
|
|
||||||
Platform.environment['XDG_RUNTIME_DIR'], // User runtime directory
|
|
||||||
Platform.environment['TMPDIR'], // App container temp (fallback)
|
|
||||||
Platform.environment['TMP'],
|
|
||||||
Platform.environment['TEMP'],
|
|
||||||
'/tmp', // System temp directory - most compatible
|
|
||||||
];
|
|
||||||
|
|
||||||
baseDirs.addAll(
|
|
||||||
otherDirs.where((dir) => dir != null && dir.isNotEmpty).cast<String>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (final baseDir in baseDirs) {
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
final socketPath = path.join(baseDir, '$kIpcBasePath-$i');
|
|
||||||
try {
|
|
||||||
final socket = await ServerSocket.bind(
|
|
||||||
InternetAddress(socketPath, type: InternetAddressType.unix),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
socket.close();
|
|
||||||
// Clean up the test socket
|
|
||||||
try {
|
|
||||||
await File(socketPath).delete();
|
|
||||||
} catch (_) {}
|
|
||||||
developer.log(
|
|
||||||
'IPC socket will be created at: $socketPath',
|
|
||||||
name: kRpcIpcLogPrefix,
|
|
||||||
);
|
|
||||||
return socketPath;
|
|
||||||
} catch (e) {
|
|
||||||
// Path not available, try next
|
|
||||||
if (i == 0) {
|
|
||||||
// Log only for the first attempt per directory
|
|
||||||
developer.log(
|
|
||||||
'IPC path $socketPath not available: $e',
|
|
||||||
name: kRpcIpcLogPrefix,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw Exception(
|
|
||||||
'No available IPC socket paths found in any temp directory',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the WebSocket server
|
|
||||||
Future<void> start() async {
|
Future<void> start() async {
|
||||||
int port = portRange[0];
|
int port = portRange[0];
|
||||||
bool wsSuccess = false;
|
bool wsSuccess = false;
|
||||||
|
|
||||||
// Start WebSocket server
|
// Start WebSocket server
|
||||||
while (port <= portRange[1]) {
|
while (port <= portRange[1]) {
|
||||||
developer.log('trying port $port', name: kRpcLogPrefix);
|
developer.log('Trying port $port', name: kRpcLogPrefix);
|
||||||
try {
|
try {
|
||||||
// Start HTTP server
|
|
||||||
_httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
|
_httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
|
||||||
developer.log('listening on $port', name: kRpcLogPrefix);
|
developer.log('Listening on $port', name: kRpcLogPrefix);
|
||||||
|
|
||||||
// Handle WebSocket upgrades
|
|
||||||
shelf_io.serveRequests(_httpServer!, (Request request) async {
|
shelf_io.serveRequests(_httpServer!, (Request request) async {
|
||||||
developer.log('new request', name: kRpcLogPrefix);
|
developer.log('New request', name: kRpcLogPrefix);
|
||||||
if (request.headers['upgrade']?.toLowerCase() == 'websocket') {
|
if (request.headers['upgrade']?.toLowerCase() == 'websocket') {
|
||||||
final handler = webSocketHandler((WebSocketChannel channel, _) {
|
final handler = webSocketHandler((WebSocketChannel channel, _) {
|
||||||
_wsSockets.add(channel);
|
_wsSockets.add(channel);
|
||||||
@@ -169,7 +82,7 @@ class ActivityRpcServer {
|
|||||||
return handler(request);
|
return handler(request);
|
||||||
}
|
}
|
||||||
developer.log(
|
developer.log(
|
||||||
'new request disposed due to not websocket',
|
'New request disposed due to not websocket',
|
||||||
name: kRpcLogPrefix,
|
name: kRpcLogPrefix,
|
||||||
);
|
);
|
||||||
return Response.notFound('Not a WebSocket request');
|
return Response.notFound('Not a WebSocket request');
|
||||||
@@ -178,12 +91,12 @@ class ActivityRpcServer {
|
|||||||
break;
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is SocketException && e.osError?.errorCode == 98) {
|
if (e is SocketException && e.osError?.errorCode == 98) {
|
||||||
// EADDRINUSE
|
|
||||||
developer.log('$port in use!', name: kRpcLogPrefix);
|
developer.log('$port in use!', name: kRpcLogPrefix);
|
||||||
} else {
|
} else {
|
||||||
developer.log('http error: $e', name: kRpcLogPrefix);
|
developer.log('HTTP error: $e', name: kRpcLogPrefix);
|
||||||
}
|
}
|
||||||
port++;
|
port++;
|
||||||
|
await Future.delayed(Duration(milliseconds: 100)); // Add delay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,27 +106,24 @@ class ActivityRpcServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start IPC server (skip on macOS due to sandboxing)
|
// Start IPC server
|
||||||
final shouldStartIpc = !Platform.isMacOS;
|
final shouldStartIpc = !Platform.isMacOS && !kIsWeb;
|
||||||
if (shouldStartIpc) {
|
if (shouldStartIpc) {
|
||||||
try {
|
try {
|
||||||
final ipcPath = await _findAvailableIpcPath();
|
_ipcServer = MultiPlatformIpcServer();
|
||||||
_ipcServer = await ServerSocket.bind(
|
|
||||||
InternetAddress(ipcPath, type: InternetAddressType.unix),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
developer.log('IPC listening at $ipcPath', name: kRpcIpcLogPrefix);
|
|
||||||
|
|
||||||
_ipcServer!.listen((Socket socket) {
|
// Set up IPC handlers
|
||||||
_onIpcConnection(socket);
|
_ipcServer!.handlePacket = (socket, packet, _) {
|
||||||
});
|
_handleIpcPacket(socket, packet);
|
||||||
|
};
|
||||||
|
|
||||||
|
await _ipcServer!.start();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
developer.log('IPC server error: $e', name: kRpcIpcLogPrefix);
|
developer.log('IPC server error: $e', name: kRpcIpcLogPrefix);
|
||||||
// Continue without IPC if it fails
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
developer.log(
|
developer.log(
|
||||||
'IPC server disabled on macOS in production mode due to sandboxing',
|
'IPC server disabled on macOS or web in production mode',
|
||||||
name: kRpcIpcLogPrefix,
|
name: kRpcIpcLogPrefix,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -223,24 +133,23 @@ class ActivityRpcServer {
|
|||||||
Future<void> stop() async {
|
Future<void> stop() async {
|
||||||
// Stop WebSocket server
|
// Stop WebSocket server
|
||||||
for (var socket in _wsSockets) {
|
for (var socket in _wsSockets) {
|
||||||
|
try {
|
||||||
await socket.sink.close();
|
await socket.sink.close();
|
||||||
|
} catch (e) {
|
||||||
|
developer.log('Error closing WebSocket: $e', name: kRpcLogPrefix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_wsSockets.clear();
|
_wsSockets.clear();
|
||||||
await _httpServer?.close();
|
await _httpServer?.close(force: true);
|
||||||
|
|
||||||
// Stop IPC server
|
// Stop IPC server
|
||||||
for (var socket in _ipcSockets) {
|
await _ipcServer?.stop();
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
_ipcSockets.clear();
|
|
||||||
await _ipcServer?.close();
|
|
||||||
|
|
||||||
developer.log('servers stopped', name: kRpcLogPrefix);
|
developer.log('Servers stopped', name: kRpcLogPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new WebSocket connection
|
// Handle new WebSocket connection
|
||||||
void _onWsConnection(WebSocketChannel socket, Request request) {
|
void _onWsConnection(WebSocketChannel socket, Request request) {
|
||||||
// Parse query parameters
|
|
||||||
final uri = request.url;
|
final uri = request.url;
|
||||||
final params = uri.queryParameters;
|
final params = uri.queryParameters;
|
||||||
final ver = int.tryParse(params['v'] ?? '1') ?? 1;
|
final ver = int.tryParse(params['v'] ?? '1') ?? 1;
|
||||||
@@ -249,43 +158,38 @@ class ActivityRpcServer {
|
|||||||
final origin = request.headers['origin'] ?? '';
|
final origin = request.headers['origin'] ?? '';
|
||||||
|
|
||||||
developer.log(
|
developer.log(
|
||||||
'new WS connection! origin: $origin, params: $params',
|
'New WS connection! origin: $origin, params: $params',
|
||||||
name: kRpcLogPrefix,
|
name: kRpcLogPrefix,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Validate origin
|
|
||||||
if (origin.isNotEmpty &&
|
if (origin.isNotEmpty &&
|
||||||
![
|
![
|
||||||
'https://discord.com',
|
'https://discord.com',
|
||||||
'https://ptb.discord.com',
|
'https://ptb.discord.com',
|
||||||
'https://canary.discord.com',
|
'https://canary.discord.com',
|
||||||
].contains(origin)) {
|
].contains(origin)) {
|
||||||
developer.log('disallowed origin: $origin', name: kRpcLogPrefix);
|
developer.log('Disallowed origin: $origin', name: kRpcLogPrefix);
|
||||||
socket.sink.close();
|
socket.sink.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate encoding
|
|
||||||
if (encoding != 'json') {
|
if (encoding != 'json') {
|
||||||
developer.log(
|
developer.log(
|
||||||
'unsupported encoding requested: $encoding',
|
'Unsupported encoding requested: $encoding',
|
||||||
name: kRpcLogPrefix,
|
name: kRpcLogPrefix,
|
||||||
);
|
);
|
||||||
socket.sink.close();
|
socket.sink.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate version
|
|
||||||
if (ver != 1) {
|
if (ver != 1) {
|
||||||
developer.log('unsupported version requested: $ver', name: kRpcLogPrefix);
|
developer.log('Unsupported version requested: $ver', name: kRpcLogPrefix);
|
||||||
socket.sink.close();
|
socket.sink.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store client info on socket
|
|
||||||
final socketWithMeta = _WsSocketWrapper(socket, clientId, encoding);
|
final socketWithMeta = _WsSocketWrapper(socket, clientId, encoding);
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
socket.stream.listen(
|
socket.stream.listen(
|
||||||
(data) => _onWsMessage(socketWithMeta, data),
|
(data) => _onWsMessage(socketWithMeta, data),
|
||||||
onError: (e) {
|
onError: (e) {
|
||||||
@@ -298,36 +202,27 @@ class ActivityRpcServer {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Notify handler of new connection
|
|
||||||
handlers['connection']?.call(socketWithMeta);
|
handlers['connection']?.call(socketWithMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new IPC connection
|
|
||||||
void _onIpcConnection(Socket socket) {
|
|
||||||
developer.log('new IPC connection!', name: kRpcIpcLogPrefix);
|
|
||||||
|
|
||||||
final socketWrapper = _IpcSocketWrapper(socket);
|
|
||||||
_ipcSockets.add(socketWrapper);
|
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
socket.listen(
|
|
||||||
(data) => _onIpcData(socketWrapper, data),
|
|
||||||
onError: (e) {
|
|
||||||
developer.log('IPC socket error: $e', name: kRpcIpcLogPrefix);
|
|
||||||
socket.close();
|
|
||||||
},
|
|
||||||
onDone: () {
|
|
||||||
developer.log('IPC socket closed', name: kRpcIpcLogPrefix);
|
|
||||||
handlers['close']?.call(socketWrapper);
|
|
||||||
_ipcSockets.remove(socketWrapper);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle incoming WebSocket message
|
// Handle incoming WebSocket message
|
||||||
void _onWsMessage(_WsSocketWrapper socket, dynamic data) {
|
Future<void> _onWsMessage(_WsSocketWrapper socket, dynamic data) async {
|
||||||
|
if (data is! String) {
|
||||||
|
developer.log(
|
||||||
|
'Invalid WebSocket message: not a string',
|
||||||
|
name: kRpcLogPrefix,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final jsonData = jsonDecode(data as String);
|
final jsonData = await compute(jsonDecode, data);
|
||||||
|
if (jsonData is! Map<String, dynamic>) {
|
||||||
|
developer.log(
|
||||||
|
'Invalid WebSocket message: not a JSON object',
|
||||||
|
name: kRpcLogPrefix,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
developer.log('WS message: $jsonData', name: kRpcLogPrefix);
|
developer.log('WS message: $jsonData', name: kRpcLogPrefix);
|
||||||
handlers['message']?.call(socket, jsonData);
|
handlers['message']?.call(socket, jsonData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -335,22 +230,8 @@ class ActivityRpcServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle incoming IPC data
|
|
||||||
void _onIpcData(_IpcSocketWrapper socket, List<int> data) {
|
|
||||||
try {
|
|
||||||
socket.addData(data);
|
|
||||||
final packets = socket.readPackets();
|
|
||||||
for (final packet in packets) {
|
|
||||||
_handleIpcPacket(socket, packet);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
developer.log('IPC data error: $e', name: kRpcIpcLogPrefix);
|
|
||||||
socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle IPC packet
|
// Handle IPC packet
|
||||||
void _handleIpcPacket(_IpcSocketWrapper socket, _IpcPacket packet) {
|
void _handleIpcPacket(IpcSocketWrapper socket, IpcPacket packet) {
|
||||||
switch (packet.type) {
|
switch (packet.type) {
|
||||||
case IpcTypes.ping:
|
case IpcTypes.ping:
|
||||||
developer.log('IPC ping received', name: kRpcIpcLogPrefix);
|
developer.log('IPC ping received', name: kRpcIpcLogPrefix);
|
||||||
@@ -359,7 +240,6 @@ class ActivityRpcServer {
|
|||||||
|
|
||||||
case IpcTypes.pong:
|
case IpcTypes.pong:
|
||||||
developer.log('IPC pong received', name: kRpcIpcLogPrefix);
|
developer.log('IPC pong received', name: kRpcIpcLogPrefix);
|
||||||
// Handle pong if needed
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IpcTypes.handshake:
|
case IpcTypes.handshake:
|
||||||
@@ -388,13 +268,12 @@ class ActivityRpcServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle IPC handshake
|
// Handle IPC handshake
|
||||||
void _onIpcHandshake(_IpcSocketWrapper socket, Map<String, dynamic> params) {
|
void _onIpcHandshake(IpcSocketWrapper socket, Map<String, dynamic> params) {
|
||||||
developer.log('IPC handshake: $params', name: kRpcIpcLogPrefix);
|
developer.log('IPC handshake: $params', name: kRpcIpcLogPrefix);
|
||||||
|
|
||||||
final ver = int.tryParse(params['v']?.toString() ?? '1') ?? 1;
|
final ver = int.tryParse(params['v']?.toString() ?? '1') ?? 1;
|
||||||
final clientId = params['client_id']?.toString() ?? '';
|
final clientId = params['client_id']?.toString() ?? '';
|
||||||
|
|
||||||
// Validate version
|
|
||||||
if (ver != 1) {
|
if (ver != 1) {
|
||||||
developer.log(
|
developer.log(
|
||||||
'IPC unsupported version requested: $ver',
|
'IPC unsupported version requested: $ver',
|
||||||
@@ -404,7 +283,6 @@ class ActivityRpcServer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate client ID
|
|
||||||
if (clientId.isEmpty) {
|
if (clientId.isEmpty) {
|
||||||
developer.log('IPC client ID required', name: kRpcIpcLogPrefix);
|
developer.log('IPC client ID required', name: kRpcIpcLogPrefix);
|
||||||
socket.closeWithCode(IpcErrorCodes.invalidClientId);
|
socket.closeWithCode(IpcErrorCodes.invalidClientId);
|
||||||
@@ -413,7 +291,6 @@ class ActivityRpcServer {
|
|||||||
|
|
||||||
socket.clientId = clientId;
|
socket.clientId = clientId;
|
||||||
|
|
||||||
// Notify handler of new connection
|
|
||||||
handlers['connection']?.call(socket);
|
handlers['connection']?.call(socket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,74 +309,6 @@ class _WsSocketWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPC wrapper
|
|
||||||
class _IpcSocketWrapper {
|
|
||||||
final Socket socket;
|
|
||||||
String clientId = '';
|
|
||||||
bool handshook = false;
|
|
||||||
final List<int> _buffer = [];
|
|
||||||
|
|
||||||
_IpcSocketWrapper(this.socket);
|
|
||||||
|
|
||||||
void addData(List<int> data) {
|
|
||||||
_buffer.addAll(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send(Map<String, dynamic> msg) {
|
|
||||||
developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix);
|
|
||||||
final packet = ActivityRpcServer.encodeIpcPacket(IpcTypes.frame, msg);
|
|
||||||
socket.add(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendPong(dynamic data) {
|
|
||||||
final packet = ActivityRpcServer.encodeIpcPacket(IpcTypes.pong, data ?? {});
|
|
||||||
socket.add(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeWithCode(int code, [String message = '']) {
|
|
||||||
final closeData = {'code': code, 'message': message};
|
|
||||||
final packet = ActivityRpcServer.encodeIpcPacket(IpcTypes.close, closeData);
|
|
||||||
socket.add(packet);
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<_IpcPacket> readPackets() {
|
|
||||||
final packets = <_IpcPacket>[];
|
|
||||||
|
|
||||||
while (_buffer.length >= 8) {
|
|
||||||
final buffer = Uint8List.fromList(_buffer);
|
|
||||||
final byteData = ByteData.view(buffer.buffer);
|
|
||||||
|
|
||||||
final type = byteData.getInt32(0, Endian.little);
|
|
||||||
final dataSize = byteData.getInt32(4, Endian.little);
|
|
||||||
|
|
||||||
if (_buffer.length < 8 + dataSize) break;
|
|
||||||
|
|
||||||
final dataBytes = _buffer.sublist(8, 8 + dataSize);
|
|
||||||
final jsonStr = utf8.decode(dataBytes);
|
|
||||||
final jsonData = jsonDecode(jsonStr);
|
|
||||||
|
|
||||||
packets.add(_IpcPacket(type, jsonData));
|
|
||||||
|
|
||||||
_buffer.removeRange(0, 8 + dataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return packets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPC Packet structure
|
|
||||||
class _IpcPacket {
|
|
||||||
final int type;
|
|
||||||
final Map<String, dynamic> data;
|
|
||||||
|
|
||||||
_IpcPacket(this.type, this.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// State management for server status and activities
|
// State management for server status and activities
|
||||||
class ServerState {
|
class ServerState {
|
||||||
final String status;
|
final String status;
|
||||||
@@ -522,7 +331,6 @@ class ServerStateNotifier extends StateNotifier<ServerState> {
|
|||||||
: super(ServerState(status: 'Server not started'));
|
: super(ServerState(status: 'Server not started'));
|
||||||
|
|
||||||
Future<void> start() async {
|
Future<void> start() async {
|
||||||
// Only start server on desktop platforms
|
|
||||||
if (!Platform.isAndroid && !Platform.isIOS && !kIsWeb) {
|
if (!Platform.isAndroid && !Platform.isIOS && !kIsWeb) {
|
||||||
try {
|
try {
|
||||||
await server.start();
|
await server.start();
|
||||||
@@ -554,9 +362,8 @@ final rpcServerStateProvider =
|
|||||||
final clientId =
|
final clientId =
|
||||||
socket is _WsSocketWrapper
|
socket is _WsSocketWrapper
|
||||||
? socket.clientId
|
? socket.clientId
|
||||||
: (socket as _IpcSocketWrapper).clientId;
|
: (socket as IpcSocketWrapper).clientId;
|
||||||
notifier.updateStatus('Client connected (ID: $clientId)');
|
notifier.updateStatus('Client connected (ID: $clientId)');
|
||||||
// Send READY event
|
|
||||||
socket.send({
|
socket.send({
|
||||||
'cmd': 'DISPATCH',
|
'cmd': 'DISPATCH',
|
||||||
'data': {
|
'data': {
|
||||||
@@ -575,7 +382,7 @@ final rpcServerStateProvider =
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
'evt': 'READY',
|
'evt': 'READY',
|
||||||
'nonce': '12345', // Should be dynamic
|
'nonce': '12345',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
'message': (socket, dynamic data) async {
|
'message': (socket, dynamic data) async {
|
||||||
@@ -583,7 +390,6 @@ final rpcServerStateProvider =
|
|||||||
notifier.addActivity(
|
notifier.addActivity(
|
||||||
'Activity: ${data['args']['activity']['details'] ?? 'Unknown'}',
|
'Activity: ${data['args']['activity']['details'] ?? 'Unknown'}',
|
||||||
);
|
);
|
||||||
// Call setRemoteActivityStatus
|
|
||||||
final label = data['args']['activity']['details'] ?? 'Unknown';
|
final label = data['args']['activity']['details'] ?? 'Unknown';
|
||||||
final appId = socket.clientId;
|
final appId = socket.clientId;
|
||||||
try {
|
try {
|
||||||
@@ -594,7 +400,6 @@ final rpcServerStateProvider =
|
|||||||
name: kRpcLogPrefix,
|
name: kRpcLogPrefix,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Echo back success
|
|
||||||
socket.send({
|
socket.send({
|
||||||
'cmd': 'SET_ACTIVITY',
|
'cmd': 'SET_ACTIVITY',
|
||||||
'data': data['args']['activity'],
|
'data': data['args']['activity'],
|
||||||
297
lib/pods/activity/ipc_server.dart
Normal file
297
lib/pods/activity/ipc_server.dart
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer' as developer;
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:dart_ipc/dart_ipc.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
const String kRpcIpcLogPrefix = 'arRPC.ipc';
|
||||||
|
|
||||||
|
// IPC Packet Types
|
||||||
|
class IpcTypes {
|
||||||
|
static const int handshake = 0;
|
||||||
|
static const int frame = 1;
|
||||||
|
static const int close = 2;
|
||||||
|
static const int ping = 3;
|
||||||
|
static const int pong = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC Close Codes
|
||||||
|
class IpcCloseCodes {
|
||||||
|
static const int closeNormal = 1000;
|
||||||
|
static const int closeUnsupported = 1003;
|
||||||
|
static const int closeAbnormal = 1006;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC Error Codes
|
||||||
|
class IpcErrorCodes {
|
||||||
|
static const int invalidClientId = 4000;
|
||||||
|
static const int invalidOrigin = 4001;
|
||||||
|
static const int rateLimited = 4002;
|
||||||
|
static const int tokenRevoked = 4003;
|
||||||
|
static const int invalidVersion = 4004;
|
||||||
|
static const int invalidEncoding = 4005;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC Packet structure
|
||||||
|
class IpcPacket {
|
||||||
|
final int type;
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
IpcPacket(this.type, this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abstract base class for IPC server
|
||||||
|
abstract class IpcServer {
|
||||||
|
final List<IpcSocketWrapper> _sockets = [];
|
||||||
|
|
||||||
|
// Encode IPC packet
|
||||||
|
static Uint8List encodeIpcPacket(int type, Map<String, dynamic> data) {
|
||||||
|
final jsonData = jsonEncode(data);
|
||||||
|
final dataBytes = utf8.encode(jsonData);
|
||||||
|
final dataSize = dataBytes.length;
|
||||||
|
|
||||||
|
final buffer = ByteData(8 + dataSize);
|
||||||
|
buffer.setInt32(0, type, Endian.little);
|
||||||
|
buffer.setInt32(4, dataSize, Endian.little);
|
||||||
|
buffer.buffer.asUint8List().setRange(8, 8 + dataSize, dataBytes);
|
||||||
|
|
||||||
|
return buffer.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> start();
|
||||||
|
Future<void> stop();
|
||||||
|
|
||||||
|
void addSocket(IpcSocketWrapper socket) {
|
||||||
|
_sockets.add(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeSocket(IpcSocketWrapper socket) {
|
||||||
|
_sockets.remove(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IpcSocketWrapper> get sockets => _sockets;
|
||||||
|
|
||||||
|
void Function(
|
||||||
|
IpcSocketWrapper socket,
|
||||||
|
IpcPacket packet,
|
||||||
|
Map<String, Function> handlers,
|
||||||
|
)?
|
||||||
|
handlePacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abstract base class for IPC socket wrapper
|
||||||
|
abstract class IpcSocketWrapper {
|
||||||
|
String clientId = '';
|
||||||
|
bool handshook = false;
|
||||||
|
final List<int> _buffer = [];
|
||||||
|
|
||||||
|
void addData(List<int> data) {
|
||||||
|
_buffer.addAll(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(Map<String, dynamic> msg);
|
||||||
|
void sendPong(dynamic data);
|
||||||
|
void close();
|
||||||
|
void closeWithCode(int code, [String message = '']);
|
||||||
|
|
||||||
|
List<IpcPacket> readPackets() {
|
||||||
|
final packets = <IpcPacket>[];
|
||||||
|
|
||||||
|
while (_buffer.length >= 8) {
|
||||||
|
final buffer = Uint8List.fromList(_buffer);
|
||||||
|
final byteData = ByteData.view(buffer.buffer);
|
||||||
|
|
||||||
|
final type = byteData.getInt32(0, Endian.little);
|
||||||
|
final dataSize = byteData.getInt32(4, Endian.little);
|
||||||
|
|
||||||
|
if (_buffer.length < 8 + dataSize) break;
|
||||||
|
|
||||||
|
final dataBytes = _buffer.sublist(8, 8 + dataSize);
|
||||||
|
final jsonStr = utf8.decode(dataBytes);
|
||||||
|
final jsonData = jsonDecode(jsonStr);
|
||||||
|
|
||||||
|
packets.add(IpcPacket(type, jsonData));
|
||||||
|
|
||||||
|
_buffer.removeRange(0, 8 + dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return packets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplatform IPC Server implementation using dart_ipc
|
||||||
|
class MultiPlatformIpcServer extends IpcServer {
|
||||||
|
StreamSubscription? _serverSubscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> start() async {
|
||||||
|
try {
|
||||||
|
final ipcPath = Platform.isWindows
|
||||||
|
? r'\\.\pipe\discord-ipc-0'
|
||||||
|
: await _findAvailableUnixIpcPath();
|
||||||
|
|
||||||
|
final serverSocket = await bind(ipcPath);
|
||||||
|
developer.log(
|
||||||
|
'IPC listening at $ipcPath',
|
||||||
|
name: kRpcIpcLogPrefix,
|
||||||
|
);
|
||||||
|
|
||||||
|
_serverSubscription = serverSocket.listen((socket) {
|
||||||
|
final socketWrapper = MultiPlatformIpcSocketWrapper(socket);
|
||||||
|
addSocket(socketWrapper);
|
||||||
|
developer.log(
|
||||||
|
'New IPC connection!',
|
||||||
|
name: kRpcIpcLogPrefix,
|
||||||
|
);
|
||||||
|
_handleIpcData(socketWrapper);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to start IPC server: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> stop() async {
|
||||||
|
for (var socket in sockets) {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (e) {
|
||||||
|
developer.log('Error closing IPC socket: $e', name: kRpcIpcLogPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sockets.clear();
|
||||||
|
_serverSubscription?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle incoming IPC data
|
||||||
|
void _handleIpcData(MultiPlatformIpcSocketWrapper socket) {
|
||||||
|
final startTime = DateTime.now();
|
||||||
|
socket.socket.listen((data) {
|
||||||
|
final readStart = DateTime.now();
|
||||||
|
socket.addData(data);
|
||||||
|
final readDuration = DateTime.now().difference(readStart).inMicroseconds;
|
||||||
|
developer.log(
|
||||||
|
'Read data took $readDuration microseconds',
|
||||||
|
name: kRpcIpcLogPrefix,
|
||||||
|
);
|
||||||
|
|
||||||
|
final packets = socket.readPackets();
|
||||||
|
for (final packet in packets) {
|
||||||
|
handlePacket?.call(socket, packet, {});
|
||||||
|
}
|
||||||
|
}, onDone: () {
|
||||||
|
developer.log('IPC connection closed', name: kRpcIpcLogPrefix);
|
||||||
|
socket.close();
|
||||||
|
}, onError: (e) {
|
||||||
|
developer.log('IPC data error: $e', name: kRpcIpcLogPrefix);
|
||||||
|
socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString());
|
||||||
|
});
|
||||||
|
final totalDuration = DateTime.now().difference(startTime).inMicroseconds;
|
||||||
|
developer.log(
|
||||||
|
'_handleIpcData took $totalDuration microseconds',
|
||||||
|
name: kRpcIpcLogPrefix,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _getMacOsSystemTmpDir() async {
|
||||||
|
final result = await Process.run('getconf', ['DARWIN_USER_TEMP_DIR']);
|
||||||
|
return (result.stdout as String).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find available IPC socket path for Unix-like systems
|
||||||
|
Future<String> _findAvailableUnixIpcPath() async {
|
||||||
|
// Build list of directories to try, with macOS-specific handling
|
||||||
|
final baseDirs = <String>[];
|
||||||
|
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
try {
|
||||||
|
final macTempDir = await _getMacOsSystemTmpDir();
|
||||||
|
if (macTempDir.isNotEmpty) {
|
||||||
|
baseDirs.add(macTempDir);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
developer.log(
|
||||||
|
'Failed to get macOS system temp dir: $e',
|
||||||
|
name: kRpcIpcLogPrefix,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other standard directories
|
||||||
|
final otherDirs = [
|
||||||
|
Platform.environment['XDG_RUNTIME_DIR'],
|
||||||
|
Platform.environment['TMPDIR'],
|
||||||
|
Platform.environment['TMP'],
|
||||||
|
Platform.environment['TEMP'],
|
||||||
|
'/tmp',
|
||||||
|
];
|
||||||
|
|
||||||
|
baseDirs.addAll(
|
||||||
|
otherDirs.where((dir) => dir != null && dir.isNotEmpty).cast<String>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final baseDir in baseDirs) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
final socketPath = path.join(baseDir, 'discord-ipc-$i');
|
||||||
|
try {
|
||||||
|
final socket = await bind(socketPath);
|
||||||
|
socket.close();
|
||||||
|
try {
|
||||||
|
await File(socketPath).delete();
|
||||||
|
} catch (_) {}
|
||||||
|
developer.log(
|
||||||
|
'IPC socket will be created at: $socketPath',
|
||||||
|
name: kRpcIpcLogPrefix,
|
||||||
|
);
|
||||||
|
return socketPath;
|
||||||
|
} catch (e) {
|
||||||
|
if (i == 0) {
|
||||||
|
developer.log(
|
||||||
|
'IPC path $socketPath not available: $e',
|
||||||
|
name: kRpcIpcLogPrefix,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Exception(
|
||||||
|
'No available IPC socket paths found in any temp directory',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplatform IPC Socket Wrapper
|
||||||
|
class MultiPlatformIpcSocketWrapper extends IpcSocketWrapper {
|
||||||
|
final dynamic socket;
|
||||||
|
|
||||||
|
MultiPlatformIpcSocketWrapper(this.socket);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void send(Map<String, dynamic> msg) {
|
||||||
|
developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix);
|
||||||
|
final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg);
|
||||||
|
socket.add(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void sendPong(dynamic data) {
|
||||||
|
final packet = IpcServer.encodeIpcPacket(IpcTypes.pong, data ?? {});
|
||||||
|
socket.add(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void closeWithCode(int code, [String message = '']) {
|
||||||
|
final closeData = {'code': code, 'message': message};
|
||||||
|
final packet = IpcServer.encodeIpcPacket(IpcTypes.close, closeData);
|
||||||
|
socket.add(packet);
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
61
lib/pods/activity/ipc_server.web.dart
Normal file
61
lib/pods/activity/ipc_server.web.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Stub implementation for web platform
|
||||||
|
// This file provides empty implementations to avoid import errors on web
|
||||||
|
|
||||||
|
// IPC Packet Types
|
||||||
|
class IpcTypes {
|
||||||
|
static const int handshake = 0;
|
||||||
|
static const int frame = 1;
|
||||||
|
static const int close = 2;
|
||||||
|
static const int ping = 3;
|
||||||
|
static const int pong = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC Close Codes
|
||||||
|
class IpcCloseCodes {
|
||||||
|
static const int closeNormal = 1000;
|
||||||
|
static const int closeUnsupported = 1003;
|
||||||
|
static const int closeAbnormal = 1006;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC Error Codes
|
||||||
|
class IpcErrorCodes {
|
||||||
|
static const int invalidClientId = 4000;
|
||||||
|
static const int invalidOrigin = 4001;
|
||||||
|
static const int rateLimited = 4002;
|
||||||
|
static const int tokenRevoked = 4003;
|
||||||
|
static const int invalidVersion = 4004;
|
||||||
|
static const int invalidEncoding = 4005;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC Packet structure
|
||||||
|
class IpcPacket {
|
||||||
|
final int type;
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
IpcPacket(this.type, this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class IpcServer {
|
||||||
|
Future<void> start() async {}
|
||||||
|
Future<void> stop() async {}
|
||||||
|
void Function(dynamic, dynamic, dynamic)? handlePacket;
|
||||||
|
void addSocket(dynamic socket) {}
|
||||||
|
void removeSocket(dynamic socket) {}
|
||||||
|
List<dynamic> get sockets => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class IpcSocketWrapper {
|
||||||
|
String clientId = '';
|
||||||
|
bool handshook = false;
|
||||||
|
|
||||||
|
void addData(List<int> data) {}
|
||||||
|
void send(Map<String, dynamic> msg) {}
|
||||||
|
void sendPong(dynamic data) {}
|
||||||
|
void close() {}
|
||||||
|
void closeWithCode(int code, [String message = '']) {}
|
||||||
|
List<dynamic> readPackets() => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultiPlatformIpcServer extends IpcServer {}
|
||||||
|
|
||||||
|
class MultiPlatformIpcSocketWrapper extends IpcSocketWrapper {}
|
||||||
@@ -30,7 +30,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
|||||||
final user = SnAccount.fromJson(response.data);
|
final user = SnAccount.fromJson(response.data);
|
||||||
state = AsyncValue.data(user);
|
state = AsyncValue.data(user);
|
||||||
|
|
||||||
if (kIsWeb || !Platform.isLinux) {
|
if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) {
|
||||||
FirebaseAnalytics.instance.setUserId(id: user.id);
|
FirebaseAnalytics.instance.setUserId(id: user.id);
|
||||||
}
|
}
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
@@ -44,7 +44,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
|||||||
: 'failedToLoadUserInfoNetwork')
|
: 'failedToLoadUserInfoNetwork')
|
||||||
.tr()
|
.tr()
|
||||||
.trim(),
|
.trim(),
|
||||||
'${error.response!.statusCode}\n${error.response?.headers}',
|
'${error.response?.statusCode ?? 'Network Error'}\n${error.response?.headers}',
|
||||||
jsonEncode(error.response?.data),
|
jsonEncode(error.response?.data),
|
||||||
].join('\n\n'),
|
].join('\n\n'),
|
||||||
iconStyle: IconStyle.error,
|
iconStyle: IconStyle.error,
|
||||||
@@ -87,7 +87,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
|||||||
final prefs = _ref.read(sharedPreferencesProvider);
|
final prefs = _ref.read(sharedPreferencesProvider);
|
||||||
await prefs.remove(kTokenPairStoreKey);
|
await prefs.remove(kTokenPairStoreKey);
|
||||||
_ref.invalidate(tokenProvider);
|
_ref.invalidate(tokenProvider);
|
||||||
if (kIsWeb || !Platform.isLinux) {
|
if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) {
|
||||||
FirebaseAnalytics.instance.setUserId(id: null);
|
FirebaseAnalytics.instance.setUserId(id: null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,8 +89,7 @@ bool get _supportsAnalytics =>
|
|||||||
kIsWeb ||
|
kIsWeb ||
|
||||||
Platform.isAndroid ||
|
Platform.isAndroid ||
|
||||||
Platform.isIOS ||
|
Platform.isIOS ||
|
||||||
Platform.isMacOS ||
|
Platform.isMacOS;
|
||||||
Platform.isWindows;
|
|
||||||
|
|
||||||
// Provider for the router
|
// Provider for the router
|
||||||
final routerProvider = Provider<GoRouter>((ref) {
|
final routerProvider = Provider<GoRouter>((ref) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/services/udid.native.dart';
|
import 'package:island/services/udid.dart' as udid;
|
||||||
import 'package:island/widgets/alert.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';
|
||||||
@@ -68,7 +68,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
try {
|
try {
|
||||||
final deviceInfoPlugin = DeviceInfoPlugin();
|
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||||
_deviceInfo = await deviceInfoPlugin.deviceInfo;
|
_deviceInfo = await deviceInfoPlugin.deviceInfo;
|
||||||
_deviceUdid = await getUdid();
|
_deviceUdid = await udid.getUdid();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@@ -174,12 +174,20 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
context,
|
context,
|
||||||
title: 'Device Information',
|
title: 'Device Information',
|
||||||
children: [
|
children: [
|
||||||
_buildInfoItem(
|
FutureBuilder<String>(
|
||||||
|
future: udid.getDeviceName(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final value =
|
||||||
|
snapshot.hasData
|
||||||
|
? snapshot.data!
|
||||||
|
: 'unknown'.tr();
|
||||||
|
return _buildInfoItem(
|
||||||
context,
|
context,
|
||||||
icon: Symbols.label,
|
icon: Symbols.label,
|
||||||
label: 'aboutDeviceName'.tr(),
|
label: 'aboutDeviceName'.tr(),
|
||||||
value:
|
value: value,
|
||||||
_deviceInfo?.data['name'] ?? 'unknown'.tr(),
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
_buildInfoItem(
|
_buildInfoItem(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
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/foundation.dart';
|
||||||
@@ -42,22 +39,6 @@ final Map<int, (String, String, IconData)> kFactorTypes = {
|
|||||||
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
|
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
|
||||||
};
|
};
|
||||||
|
|
||||||
Future<String?> getDeviceName() async {
|
|
||||||
if (kIsWeb) return null;
|
|
||||||
String? name;
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
final deviceInfo = await DeviceInfoPlugin().iosInfo;
|
|
||||||
name = deviceInfo.name;
|
|
||||||
} else if (Platform.isAndroid) {
|
|
||||||
final deviceInfo = await DeviceInfoPlugin().androidInfo;
|
|
||||||
name = deviceInfo.name;
|
|
||||||
} else if (Platform.isWindows) {
|
|
||||||
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
|
|
||||||
name = deviceInfo.computerName;
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginScreen extends HookConsumerWidget {
|
class LoginScreen extends HookConsumerWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -289,7 +289,6 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
bool? _withAttachments;
|
bool? _withAttachments;
|
||||||
|
|
||||||
late final String _roomId;
|
late final String _roomId;
|
||||||
int _currentPage = 0;
|
|
||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
bool _hasMore = true;
|
bool _hasMore = true;
|
||||||
bool _isSyncing = false;
|
bool _isSyncing = false;
|
||||||
@@ -565,28 +564,28 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadInitial() async {
|
Future<void> loadInitial() async {
|
||||||
developer.log('Loading initial messages', name: 'MessagesNotifier');
|
developer.log('Loading initial messages', name: 'MessagesNotifier');
|
||||||
if (_searchQuery == null || _searchQuery!.isEmpty) {
|
if (_searchQuery == null || _searchQuery!.isEmpty) {
|
||||||
syncMessages();
|
syncMessages();
|
||||||
}
|
}
|
||||||
final messages = await _getCachedMessages(offset: 0, take: 100);
|
|
||||||
_currentPage = 0;
|
|
||||||
_hasMore = messages.length == _pageSize;
|
|
||||||
state = AsyncValue.data(messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadMore() async {
|
final messages = await _getCachedMessages(offset: 0, take: _pageSize);
|
||||||
|
|
||||||
|
_hasMore = messages.length == _pageSize;
|
||||||
|
|
||||||
|
state = AsyncValue.data(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadMore() async {
|
||||||
if (!_hasMore || state is AsyncLoading) return;
|
if (!_hasMore || state is AsyncLoading) return;
|
||||||
developer.log('Loading more messages', name: 'MessagesNotifier');
|
developer.log('Loading more messages', name: 'MessagesNotifier');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final currentMessages = state.value ?? [];
|
final currentMessages = state.value ?? [];
|
||||||
_currentPage++;
|
final offset = currentMessages.length;
|
||||||
final newMessages = await listMessages(
|
|
||||||
offset: _currentPage * _pageSize,
|
final newMessages = await listMessages(offset: offset, take: _pageSize);
|
||||||
take: _pageSize,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newMessages.isEmpty || newMessages.length < _pageSize) {
|
if (newMessages.isEmpty || newMessages.length < _pageSize) {
|
||||||
_hasMore = false;
|
_hasMore = false;
|
||||||
@@ -603,9 +602,8 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
stackTrace: stackTrace,
|
stackTrace: stackTrace,
|
||||||
);
|
);
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
_currentPage--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> sendMessage(
|
Future<void> sendMessage(
|
||||||
String content,
|
String content,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class NotificationUnreadCountNotifier
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
final response = await client.get('/pusher/notifications/count');
|
final response = await client.get('/ring/notifications/count');
|
||||||
return (response.data as num).toInt();
|
return (response.data as num).toInt();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -89,7 +89,7 @@ class NotificationListNotifier extends _$NotificationListNotifier
|
|||||||
final queryParams = {'offset': offset, 'take': _pageSize};
|
final queryParams = {'offset': offset, 'take': _pageSize};
|
||||||
|
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
'/pusher/notifications',
|
'/ring/notifications',
|
||||||
queryParameters: queryParams,
|
queryParameters: queryParams,
|
||||||
);
|
);
|
||||||
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
@@ -121,7 +121,7 @@ class NotificationScreen extends HookConsumerWidget {
|
|||||||
Future<void> markAllRead() async {
|
Future<void> markAllRead() async {
|
||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
await apiClient.post('/pusher/notifications/all/read');
|
await apiClient.post('/ring/notifications/all/read');
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
hideLoadingModal(context);
|
hideLoadingModal(context);
|
||||||
ref.invalidate(notificationListNotifierProvider);
|
ref.invalidate(notificationListNotifierProvider);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.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';
|
||||||
|
|||||||
@@ -48,11 +48,12 @@ class TrayService {
|
|||||||
void handleAction(MenuItem item) {
|
void handleAction(MenuItem item) {
|
||||||
switch (item.key) {
|
switch (item.key) {
|
||||||
case 'show_window':
|
case 'show_window':
|
||||||
if (appWindow.isVisible) {
|
() async {
|
||||||
appWindow.restore();
|
|
||||||
} else {
|
|
||||||
appWindow.show();
|
appWindow.show();
|
||||||
}
|
appWindow.restore();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 32));
|
||||||
|
appWindow.show();
|
||||||
|
}();
|
||||||
break;
|
break;
|
||||||
case 'exit_app':
|
case 'exit_app':
|
||||||
appWindow.close();
|
appWindow.close();
|
||||||
|
|||||||
@@ -1,230 +1,47 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:island/main.dart';
|
|
||||||
import 'package:island/route.dart';
|
|
||||||
import 'package:island/models/account.dart';
|
|
||||||
import 'package:island/pods/websocket.dart';
|
|
||||||
import 'package:island/widgets/app_notification.dart';
|
|
||||||
import 'package:top_snackbar_flutter/top_snack_bar.dart';
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
// Conditional imports based on platform
|
||||||
FlutterLocalNotificationsPlugin();
|
import 'notify.windows.dart' as windows_notify;
|
||||||
|
import 'notify.universal.dart' as universal_notify;
|
||||||
AppLifecycleState _appLifecycleState = AppLifecycleState.resumed;
|
|
||||||
|
|
||||||
void _onAppLifecycleChanged(AppLifecycleState state) {
|
|
||||||
_appLifecycleState = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Platform-specific delegation
|
||||||
Future<void> initializeLocalNotifications() async {
|
Future<void> initializeLocalNotifications() async {
|
||||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
if (Platform.isWindows) {
|
||||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
return windows_notify.initializeLocalNotifications();
|
||||||
|
|
||||||
const DarwinInitializationSettings initializationSettingsIOS =
|
|
||||||
DarwinInitializationSettings();
|
|
||||||
|
|
||||||
const DarwinInitializationSettings initializationSettingsMacOS =
|
|
||||||
DarwinInitializationSettings();
|
|
||||||
|
|
||||||
const LinuxInitializationSettings initializationSettingsLinux =
|
|
||||||
LinuxInitializationSettings(defaultActionName: 'Open notification');
|
|
||||||
|
|
||||||
const WindowsInitializationSettings initializationSettingsWindows =
|
|
||||||
WindowsInitializationSettings(
|
|
||||||
appName: 'Island',
|
|
||||||
appUserModelId: 'dev.solsynth.solian',
|
|
||||||
guid: 'dev.solsynth.solian',
|
|
||||||
);
|
|
||||||
|
|
||||||
const InitializationSettings initializationSettings = InitializationSettings(
|
|
||||||
android: initializationSettingsAndroid,
|
|
||||||
iOS: initializationSettingsIOS,
|
|
||||||
macOS: initializationSettingsMacOS,
|
|
||||||
linux: initializationSettingsLinux,
|
|
||||||
windows: initializationSettingsWindows,
|
|
||||||
);
|
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(
|
|
||||||
initializationSettings,
|
|
||||||
onDidReceiveNotificationResponse: (NotificationResponse response) async {
|
|
||||||
final payload = response.payload;
|
|
||||||
if (payload != null) {
|
|
||||||
if (payload.startsWith('/')) {
|
|
||||||
// In-app routes
|
|
||||||
rootNavigatorKey.currentContext?.push(payload);
|
|
||||||
} else {
|
} else {
|
||||||
// External URLs
|
return universal_notify.initializeLocalNotifications();
|
||||||
launchUrlString(payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addObserver(
|
|
||||||
LifecycleEventHandler(onAppLifecycleChanged: _onAppLifecycleChanged),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class LifecycleEventHandler extends WidgetsBindingObserver {
|
|
||||||
final void Function(AppLifecycleState) onAppLifecycleChanged;
|
|
||||||
|
|
||||||
LifecycleEventHandler({required this.onAppLifecycleChanged});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
onAppLifecycleChanged(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamSubscription<WebSocketPacket> setupNotificationListener(
|
StreamSubscription setupNotificationListener(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
) {
|
) {
|
||||||
final ws = ref.watch(websocketProvider);
|
if (Platform.isWindows) {
|
||||||
return ws.dataStream.listen((pkt) async {
|
return windows_notify.setupNotificationListener(context, ref);
|
||||||
if (pkt.type == "notifications.new") {
|
|
||||||
final notification = SnNotification.fromJson(pkt.data!);
|
|
||||||
if (_appLifecycleState == AppLifecycleState.resumed) {
|
|
||||||
// App is focused, show in-app notification
|
|
||||||
log(
|
|
||||||
'[Notification] Showing in-app notification: ${notification.title}',
|
|
||||||
);
|
|
||||||
showTopSnackBar(
|
|
||||||
globalOverlay.currentState!,
|
|
||||||
Center(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 480),
|
|
||||||
child: NotificationCard(notification: notification),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
if (notification.meta['action_uri'] != null) {
|
|
||||||
var uri = notification.meta['action_uri'] as String;
|
|
||||||
if (uri.startsWith('/')) {
|
|
||||||
// In-app routes
|
|
||||||
rootNavigatorKey.currentContext?.push(
|
|
||||||
notification.meta['action_uri'],
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// External URLs
|
return universal_notify.setupNotificationListener(context, ref);
|
||||||
launchUrlString(uri);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissed: () {},
|
|
||||||
dismissType: DismissType.onSwipe,
|
|
||||||
displayDuration: const Duration(seconds: 5),
|
|
||||||
snackBarPosition: SnackBarPosition.top,
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
top:
|
|
||||||
(!kIsWeb &&
|
|
||||||
(Platform.isMacOS ||
|
|
||||||
Platform.isWindows ||
|
|
||||||
Platform.isLinux))
|
|
||||||
? 28
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
: MediaQuery.of(context).padding.top + 16,
|
|
||||||
bottom: 16,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// App is in background, show system notification (only on supported platforms)
|
|
||||||
if (!kIsWeb && !Platform.isIOS) {
|
|
||||||
log(
|
|
||||||
'[Notification] Showing system notification: ${notification.title}',
|
|
||||||
);
|
|
||||||
const AndroidNotificationDetails androidNotificationDetails =
|
|
||||||
AndroidNotificationDetails(
|
|
||||||
'channel_id',
|
|
||||||
'channel_name',
|
|
||||||
channelDescription: 'channel_description',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.high,
|
|
||||||
ticker: 'ticker',
|
|
||||||
);
|
|
||||||
const NotificationDetails notificationDetails = NotificationDetails(
|
|
||||||
android: androidNotificationDetails,
|
|
||||||
);
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
|
||||||
0,
|
|
||||||
notification.title,
|
|
||||||
notification.content,
|
|
||||||
notificationDetails,
|
|
||||||
payload: notification.meta['action_uri'] as String?,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log(
|
|
||||||
'[Notification] Skipping system notification for unsupported platform: ${notification.title}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> subscribePushNotification(
|
Future<void> subscribePushNotification(
|
||||||
Dio apiClient, {
|
Dio apiClient, {
|
||||||
bool detailedErrors = false,
|
bool detailedErrors = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (!kIsWeb && Platform.isLinux) {
|
if (Platform.isWindows) {
|
||||||
return;
|
return windows_notify.subscribePushNotification(
|
||||||
}
|
|
||||||
await FirebaseMessaging.instance.requestPermission(
|
|
||||||
alert: true,
|
|
||||||
badge: true,
|
|
||||||
sound: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
String? deviceToken;
|
|
||||||
if (kIsWeb) {
|
|
||||||
deviceToken = await FirebaseMessaging.instance.getToken(
|
|
||||||
vapidKey:
|
|
||||||
"BFN2mkqyeI6oi4d2PAV4pfNyG3Jy0FBEblmmPrjmP0r5lHOPrxrcqLIWhM21R_cicF-j4Xhtr1kyDyDgJYRPLgU",
|
|
||||||
);
|
|
||||||
} else if (Platform.isAndroid) {
|
|
||||||
deviceToken = await FirebaseMessaging.instance.getToken();
|
|
||||||
} else if (Platform.isIOS) {
|
|
||||||
deviceToken = await FirebaseMessaging.instance.getAPNSToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
FirebaseMessaging.instance.onTokenRefresh
|
|
||||||
.listen((fcmToken) {
|
|
||||||
_putTokenToRemote(apiClient, fcmToken, 1);
|
|
||||||
})
|
|
||||||
.onError((err) {
|
|
||||||
log("Failed to get firebase cloud messaging push token: $err");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deviceToken != null) {
|
|
||||||
_putTokenToRemote(
|
|
||||||
apiClient,
|
apiClient,
|
||||||
deviceToken,
|
detailedErrors: detailedErrors,
|
||||||
!kIsWeb && (Platform.isIOS || Platform.isMacOS) ? 0 : 1,
|
);
|
||||||
|
} else {
|
||||||
|
return universal_notify.subscribePushNotification(
|
||||||
|
apiClient,
|
||||||
|
detailedErrors: detailedErrors,
|
||||||
);
|
);
|
||||||
} else if (detailedErrors) {
|
|
||||||
throw Exception("Failed to get device token for push notifications.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _putTokenToRemote(
|
|
||||||
Dio apiClient,
|
|
||||||
String token,
|
|
||||||
int provider,
|
|
||||||
) async {
|
|
||||||
await apiClient.put(
|
|
||||||
"/pusher/notifications/subscription",
|
|
||||||
data: {"provider": provider, "device_token": token},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
232
lib/services/notify.universal.dart
Normal file
232
lib/services/notify.universal.dart
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:island/main.dart';
|
||||||
|
import 'package:island/route.dart';
|
||||||
|
import 'package:island/models/account.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
|
import 'package:island/widgets/app_notification.dart';
|
||||||
|
import 'package:top_snackbar_flutter/top_snack_bar.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
|
AppLifecycleState _appLifecycleState = AppLifecycleState.resumed;
|
||||||
|
|
||||||
|
void _onAppLifecycleChanged(AppLifecycleState state) {
|
||||||
|
_appLifecycleState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initializeLocalNotifications() async {
|
||||||
|
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||||
|
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
|
|
||||||
|
const DarwinInitializationSettings initializationSettingsIOS =
|
||||||
|
DarwinInitializationSettings();
|
||||||
|
|
||||||
|
const DarwinInitializationSettings initializationSettingsMacOS =
|
||||||
|
DarwinInitializationSettings();
|
||||||
|
|
||||||
|
const LinuxInitializationSettings initializationSettingsLinux =
|
||||||
|
LinuxInitializationSettings(defaultActionName: 'Open notification');
|
||||||
|
|
||||||
|
const WindowsInitializationSettings initializationSettingsWindows =
|
||||||
|
WindowsInitializationSettings(
|
||||||
|
appName: 'Island',
|
||||||
|
appUserModelId: 'dev.solsynth.solian',
|
||||||
|
guid: 'dev.solsynth.solian',
|
||||||
|
);
|
||||||
|
|
||||||
|
const InitializationSettings initializationSettings = InitializationSettings(
|
||||||
|
android: initializationSettingsAndroid,
|
||||||
|
iOS: initializationSettingsIOS,
|
||||||
|
macOS: initializationSettingsMacOS,
|
||||||
|
linux: initializationSettingsLinux,
|
||||||
|
windows: initializationSettingsWindows,
|
||||||
|
);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.initialize(
|
||||||
|
initializationSettings,
|
||||||
|
onDidReceiveNotificationResponse: (NotificationResponse response) async {
|
||||||
|
final payload = response.payload;
|
||||||
|
if (payload != null) {
|
||||||
|
if (payload.startsWith('/')) {
|
||||||
|
// In-app routes
|
||||||
|
rootNavigatorKey.currentContext?.push(payload);
|
||||||
|
} else {
|
||||||
|
// External URLs
|
||||||
|
launchUrlString(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addObserver(
|
||||||
|
LifecycleEventHandler(onAppLifecycleChanged: _onAppLifecycleChanged),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LifecycleEventHandler extends WidgetsBindingObserver {
|
||||||
|
final void Function(AppLifecycleState) onAppLifecycleChanged;
|
||||||
|
|
||||||
|
LifecycleEventHandler({required this.onAppLifecycleChanged});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
onAppLifecycleChanged(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<WebSocketPacket> setupNotificationListener(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
) {
|
||||||
|
final ws = ref.watch(websocketProvider);
|
||||||
|
return ws.dataStream.listen((pkt) async {
|
||||||
|
if (pkt.type == "notifications.new") {
|
||||||
|
final notification = SnNotification.fromJson(pkt.data!);
|
||||||
|
if (_appLifecycleState == AppLifecycleState.resumed) {
|
||||||
|
// App is focused, show in-app notification
|
||||||
|
log(
|
||||||
|
'[Notification] Showing in-app notification: ${notification.title}',
|
||||||
|
);
|
||||||
|
showTopSnackBar(
|
||||||
|
globalOverlay.currentState!,
|
||||||
|
Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 480),
|
||||||
|
child: NotificationCard(notification: notification),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (notification.meta['action_uri'] != null) {
|
||||||
|
var uri = notification.meta['action_uri'] as String;
|
||||||
|
if (uri.startsWith('/')) {
|
||||||
|
// In-app routes
|
||||||
|
rootNavigatorKey.currentContext?.push(
|
||||||
|
notification.meta['action_uri'],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// External URLs
|
||||||
|
launchUrlString(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissed: () {},
|
||||||
|
dismissType: DismissType.onSwipe,
|
||||||
|
displayDuration: const Duration(seconds: 5),
|
||||||
|
snackBarPosition: SnackBarPosition.top,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top:
|
||||||
|
(!kIsWeb &&
|
||||||
|
(Platform.isMacOS ||
|
||||||
|
Platform.isWindows ||
|
||||||
|
Platform.isLinux))
|
||||||
|
? 28
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
: MediaQuery.of(context).padding.top + 16,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// App is in background, show system notification (only on supported platforms)
|
||||||
|
if (!kIsWeb && !Platform.isIOS) {
|
||||||
|
log(
|
||||||
|
'[Notification] Showing system notification: ${notification.title}',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use flutter_local_notifications for universal platforms
|
||||||
|
const AndroidNotificationDetails androidNotificationDetails =
|
||||||
|
AndroidNotificationDetails(
|
||||||
|
'channel_id',
|
||||||
|
'channel_name',
|
||||||
|
channelDescription: 'channel_description',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.high,
|
||||||
|
ticker: 'ticker',
|
||||||
|
);
|
||||||
|
const NotificationDetails notificationDetails = NotificationDetails(
|
||||||
|
android: androidNotificationDetails,
|
||||||
|
);
|
||||||
|
await flutterLocalNotificationsPlugin.show(
|
||||||
|
0,
|
||||||
|
notification.title,
|
||||||
|
notification.content,
|
||||||
|
notificationDetails,
|
||||||
|
payload: notification.meta['action_uri'] as String?,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log(
|
||||||
|
'[Notification] Skipping system notification for unsupported platform: ${notification.title}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> subscribePushNotification(
|
||||||
|
Dio apiClient, {
|
||||||
|
bool detailedErrors = false,
|
||||||
|
}) async {
|
||||||
|
if (!kIsWeb && Platform.isLinux) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await FirebaseMessaging.instance.requestPermission(
|
||||||
|
alert: true,
|
||||||
|
badge: true,
|
||||||
|
sound: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
String? deviceToken;
|
||||||
|
if (kIsWeb) {
|
||||||
|
deviceToken = await FirebaseMessaging.instance.getToken(
|
||||||
|
vapidKey:
|
||||||
|
"BFN2mkqyeI6oi4d2PAV4pfNyG3Jy0FBEblmmPrjmP0r5lHOPrxrcqLIWhM21R_cicF-j4Xhtr1kyDyDgJYRPLgU",
|
||||||
|
);
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
deviceToken = await FirebaseMessaging.instance.getToken();
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
deviceToken = await FirebaseMessaging.instance.getAPNSToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
FirebaseMessaging.instance.onTokenRefresh
|
||||||
|
.listen((fcmToken) {
|
||||||
|
_putTokenToRemote(apiClient, fcmToken, 1);
|
||||||
|
})
|
||||||
|
.onError((err) {
|
||||||
|
log("Failed to get firebase cloud messaging push token: $err");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deviceToken != null) {
|
||||||
|
_putTokenToRemote(
|
||||||
|
apiClient,
|
||||||
|
deviceToken,
|
||||||
|
!kIsWeb && (Platform.isIOS || Platform.isMacOS) ? 0 : 1,
|
||||||
|
);
|
||||||
|
} else if (detailedErrors) {
|
||||||
|
throw Exception("Failed to get device token for push notifications.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _putTokenToRemote(
|
||||||
|
Dio apiClient,
|
||||||
|
String token,
|
||||||
|
int provider,
|
||||||
|
) async {
|
||||||
|
await apiClient.put(
|
||||||
|
"/ring/notifications/subscription",
|
||||||
|
data: {"provider": provider, "device_token": token},
|
||||||
|
);
|
||||||
|
}
|
||||||
176
lib/services/notify.windows.dart
Normal file
176
lib/services/notify.windows.dart
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:island/main.dart';
|
||||||
|
import 'package:island/route.dart';
|
||||||
|
import 'package:island/models/account.dart';
|
||||||
|
import 'package:island/pods/websocket.dart';
|
||||||
|
import 'package:island/widgets/app_notification.dart';
|
||||||
|
import 'package:top_snackbar_flutter/top_snack_bar.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
import 'package:windows_notification/windows_notification.dart'
|
||||||
|
as windows_notification;
|
||||||
|
import 'package:windows_notification/notification_message.dart';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
// Windows notification instance
|
||||||
|
windows_notification.WindowsNotification? windowsNotification;
|
||||||
|
|
||||||
|
AppLifecycleState _appLifecycleState = AppLifecycleState.resumed;
|
||||||
|
|
||||||
|
void _onAppLifecycleChanged(AppLifecycleState state) {
|
||||||
|
_appLifecycleState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initializeLocalNotifications() async {
|
||||||
|
// Initialize Windows notification for Windows platform
|
||||||
|
windowsNotification = windows_notification.WindowsNotification(
|
||||||
|
applicationId: 'dev.solsynth.solian',
|
||||||
|
);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addObserver(
|
||||||
|
LifecycleEventHandler(onAppLifecycleChanged: _onAppLifecycleChanged),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LifecycleEventHandler extends WidgetsBindingObserver {
|
||||||
|
final void Function(AppLifecycleState) onAppLifecycleChanged;
|
||||||
|
|
||||||
|
LifecycleEventHandler({required this.onAppLifecycleChanged});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
onAppLifecycleChanged(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<WebSocketPacket> setupNotificationListener(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
) {
|
||||||
|
final ws = ref.watch(websocketProvider);
|
||||||
|
return ws.dataStream.listen((pkt) async {
|
||||||
|
if (pkt.type == "notifications.new") {
|
||||||
|
final notification = SnNotification.fromJson(pkt.data!);
|
||||||
|
if (_appLifecycleState == AppLifecycleState.resumed) {
|
||||||
|
// App is focused, show in-app notification
|
||||||
|
log(
|
||||||
|
'[Notification] Showing in-app notification: ${notification.title}',
|
||||||
|
);
|
||||||
|
showTopSnackBar(
|
||||||
|
globalOverlay.currentState!,
|
||||||
|
Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 480),
|
||||||
|
child: NotificationCard(notification: notification),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (notification.meta['action_uri'] != null) {
|
||||||
|
var uri = notification.meta['action_uri'] as String;
|
||||||
|
if (uri.startsWith('/')) {
|
||||||
|
// In-app routes
|
||||||
|
rootNavigatorKey.currentContext?.push(
|
||||||
|
notification.meta['action_uri'],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// External URLs
|
||||||
|
launchUrlString(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissed: () {},
|
||||||
|
dismissType: DismissType.onSwipe,
|
||||||
|
displayDuration: const Duration(seconds: 5),
|
||||||
|
snackBarPosition: SnackBarPosition.top,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
top: 28, // Windows specific padding
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// App is in background, show Windows system notification
|
||||||
|
log(
|
||||||
|
'[Notification] Showing Windows system notification: ${notification.title}',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (windowsNotification != null) {
|
||||||
|
// Use Windows notification for Windows platform
|
||||||
|
final notificationMessage = NotificationMessage.fromPluginTemplate(
|
||||||
|
DateTime.now().millisecondsSinceEpoch.toString(), // unique id
|
||||||
|
notification.title,
|
||||||
|
notification.content,
|
||||||
|
launch: notification.meta['action_uri'] as String?,
|
||||||
|
);
|
||||||
|
await windowsNotification!.showNotificationPluginTemplate(
|
||||||
|
notificationMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> subscribePushNotification(
|
||||||
|
Dio apiClient, {
|
||||||
|
bool detailedErrors = false,
|
||||||
|
}) async {
|
||||||
|
if (!kIsWeb && Platform.isLinux) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await FirebaseMessaging.instance.requestPermission(
|
||||||
|
alert: true,
|
||||||
|
badge: true,
|
||||||
|
sound: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
String? deviceToken;
|
||||||
|
if (kIsWeb) {
|
||||||
|
deviceToken = await FirebaseMessaging.instance.getToken(
|
||||||
|
vapidKey:
|
||||||
|
"BFN2mkqyeI6oi4d2PAV4pfNyG3Jy0FBEblmmPrjmP0r5lHOPrxrcqLIWhM21R_cicF-j4Xhtr1kyDyDgJYRPLgU",
|
||||||
|
);
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
deviceToken = await FirebaseMessaging.instance.getToken();
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
deviceToken = await FirebaseMessaging.instance.getAPNSToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
FirebaseMessaging.instance.onTokenRefresh
|
||||||
|
.listen((fcmToken) {
|
||||||
|
_putTokenToRemote(apiClient, fcmToken, 1);
|
||||||
|
})
|
||||||
|
.onError((err) {
|
||||||
|
log("Failed to get firebase cloud messaging push token: $err");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deviceToken != null) {
|
||||||
|
_putTokenToRemote(
|
||||||
|
apiClient,
|
||||||
|
deviceToken,
|
||||||
|
!kIsWeb && (Platform.isIOS || Platform.isMacOS) ? 0 : 1,
|
||||||
|
);
|
||||||
|
} else if (detailedErrors) {
|
||||||
|
throw Exception("Failed to get device token for push notifications.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _putTokenToRemote(
|
||||||
|
Dio apiClient,
|
||||||
|
String token,
|
||||||
|
int provider,
|
||||||
|
) async {
|
||||||
|
await apiClient.put(
|
||||||
|
"/ring/notifications/subscription",
|
||||||
|
data: {"provider": provider, "device_token": token},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ Future<void> initializeTzdb() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getMachineTz() async {
|
Future<String> getMachineTz() async {
|
||||||
return await FlutterTimezone.getLocalTimezone();
|
return (await FlutterTimezone.getLocalTimezone()).identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getAvailableTz() {
|
List<String> getAvailableTz() {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Future<void> initializeTzdb() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getMachineTz() async {
|
Future<String> getMachineTz() async {
|
||||||
return await FlutterTimezone.getLocalTimezone();
|
return (await FlutterTimezone.getLocalTimezone()).identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getAvailableTz() {
|
List<String> getAvailableTz() {
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export 'udid.native.dart' if (dart.library.html) 'udid.web.dart';
|
export 'udid.native.dart'
|
||||||
|
if (dart.library.html) 'udid.web.dart'
|
||||||
|
if (dart.library.io) 'udid.native.dart';
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter_udid/flutter_udid.dart';
|
import 'package:flutter_udid/flutter_udid.dart';
|
||||||
|
|
||||||
String? _cachedUdid;
|
String? _cachedUdid;
|
||||||
@@ -9,3 +12,18 @@ Future<String> getUdid() async {
|
|||||||
_cachedUdid = await FlutterUdid.consistentUdid;
|
_cachedUdid = await FlutterUdid.consistentUdid;
|
||||||
return _cachedUdid!;
|
return _cachedUdid!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getDeviceName() async {
|
||||||
|
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||||
|
return androidInfo.device;
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||||
|
return iosInfo.name;
|
||||||
|
} else if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||||
|
return Platform.localHostname;
|
||||||
|
} else {
|
||||||
|
return 'unknown'.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,3 +9,18 @@ Future<String> getUdid() async {
|
|||||||
final hash = sha256.convert(bytes);
|
final hash = sha256.convert(bytes);
|
||||||
return hash.toString();
|
return hash.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getDeviceName() async {
|
||||||
|
final userAgent = window.navigator.userAgent;
|
||||||
|
if (userAgent.contains('Chrome') && !userAgent.contains('Edg')) {
|
||||||
|
return 'Chrome';
|
||||||
|
} else if (userAgent.contains('Firefox')) {
|
||||||
|
return 'Firefox';
|
||||||
|
} else if (userAgent.contains('Safari') && !userAgent.contains('Chrome')) {
|
||||||
|
return 'Safari';
|
||||||
|
} else if (userAgent.contains('Edg')) {
|
||||||
|
return 'Edge';
|
||||||
|
} else {
|
||||||
|
return 'Browser';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:bitsdojo_window/bitsdojo_window.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:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/activity_rpc.dart';
|
import 'package:island/pods/activity/activity_rpc.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/screens/tray_manager.dart';
|
import 'package:island/screens/tray_manager.dart';
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
spacing: 3,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
@@ -244,7 +244,7 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
loading: () => Text('checkInNone').tr().fontSize(15).bold(),
|
loading: () => Text('checkInNone').tr().fontSize(15).bold(),
|
||||||
error: (err, stack) => Text('error').tr().fontSize(15).bold(),
|
error: (err, stack) => Text('error').tr().fontSize(15).bold(),
|
||||||
),
|
),
|
||||||
),
|
).padding(right: 4),
|
||||||
IconButton.outlined(
|
IconButton.outlined(
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ String _parseRemoteError(DioException err) {
|
|||||||
String? message;
|
String? message;
|
||||||
if (err.response?.data is String) {
|
if (err.response?.data is String) {
|
||||||
message = err.response?.data;
|
message = err.response?.data;
|
||||||
|
} else if (err.response?.data?['message'] != null) {
|
||||||
|
message = <String?>[
|
||||||
|
err.response?.data?['message']?.toString(),
|
||||||
|
err.response?.data?['detail']?.toString(),
|
||||||
|
].where((e) => e != null).cast<String>().map((e) => e.trim()).join('\n');
|
||||||
} else if (err.response?.data?['errors'] != null) {
|
} else if (err.response?.data?['errors'] != null) {
|
||||||
final errors = err.response?.data['errors'] as Map<String, dynamic>;
|
final errors = err.response?.data['errors'] as Map<String, dynamic>;
|
||||||
message = errors.values
|
message = errors.values
|
||||||
|
|||||||
@@ -8,17 +8,26 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
|
|
||||||
String _parseRemoteError(DioException err) {
|
String _parseRemoteError(DioException err) {
|
||||||
log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}');
|
log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}');
|
||||||
if (err.response?.data is String) return err.response?.data;
|
String? message;
|
||||||
if (err.response?.data?['errors'] != null) {
|
if (err.response?.data is String) {
|
||||||
|
message = err.response?.data;
|
||||||
|
} else if (err.response?.data?['message'] != null) {
|
||||||
|
message = <String?>[
|
||||||
|
err.response?.data?['message']?.toString(),
|
||||||
|
err.response?.data?['detail']?.toString(),
|
||||||
|
].where((e) => e != null).cast<String>().map((e) => e.trim()).join('\n');
|
||||||
|
} else if (err.response?.data?['errors'] != null) {
|
||||||
final errors = err.response?.data['errors'] as Map<String, dynamic>;
|
final errors = err.response?.data['errors'] as Map<String, dynamic>;
|
||||||
return errors.values
|
message = errors.values
|
||||||
.map(
|
.map(
|
||||||
(ele) =>
|
(ele) =>
|
||||||
(ele as List<dynamic>).map((ele) => ele.toString()).join('\n'),
|
(ele as List<dynamic>).map((ele) => ele.toString()).join('\n'),
|
||||||
)
|
)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
}
|
}
|
||||||
return err.message ?? err.toString();
|
if (message == null || message.isEmpty) message = err.response?.statusMessage;
|
||||||
|
message ??= err.message;
|
||||||
|
return message ?? err.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showErrorAlert(dynamic err) async {
|
void showErrorAlert(dynamic err) async {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'post_award_history_sheet.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$postAwardListNotifierHash() =>
|
String _$postAwardListNotifierHash() =>
|
||||||
r'492ae59a5dbbfb5c98f863f036023193b6e08668';
|
r'834d08f90ef352a2dfb0192455c75b1620e859c2';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -111,9 +111,9 @@ PODS:
|
|||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
- flutter_webrtc (1.1.0):
|
- flutter_webrtc (1.2.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 137.7151.03)
|
- WebRTC-SDK (= 137.7151.04)
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -175,7 +175,7 @@ PODS:
|
|||||||
- livekit_client (2.5.0):
|
- livekit_client (2.5.0):
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 137.7151.03)
|
- WebRTC-SDK (= 137.7151.04)
|
||||||
- local_auth_darwin (0.0.1):
|
- local_auth_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -247,7 +247,7 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (137.7151.03)
|
- WebRTC-SDK (137.7151.04)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
||||||
@@ -419,17 +419,17 @@ SPEC CHECKSUMS:
|
|||||||
flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0
|
flutter_local_notifications: 4bf37a31afde695b56091b4ae3e4d9c7a7e6cda0
|
||||||
flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
|
flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
|
||||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||||
flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8
|
flutter_timezone: d272288c69082ad571630e0d17140b3d6b93dc0c
|
||||||
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
|
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
|
||||||
flutter_webrtc: 1ce7fe9a42f085286378355a575e682edd7f114d
|
flutter_webrtc: 718eae22a371cd94e5d56aa4f301443ebc5bb737
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
GoogleAppMeasurement: 09f341dfa8527d1612a09cbfe809a242c0b737af
|
GoogleAppMeasurement: 09f341dfa8527d1612a09cbfe809a242c0b737af
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||||
livekit_client: 5a5c0f1081978542bbf9a986c7ac9bffcdb73906
|
livekit_client: 95b4a47f51f98a8be3a181c3fa251be7823dddd4
|
||||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
||||||
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
@@ -451,8 +451,8 @@ SPEC CHECKSUMS:
|
|||||||
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
|
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
|
||||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||||
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
||||||
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
|
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||||
WebRTC-SDK: 69d4e56b0b4b27d788e87bab9b9a1326ed05b1e3
|
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
||||||
|
|
||||||
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>Default</string>
|
<string>Default</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.device-information.user-assigned-device-name</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.cs.allow-jit</key>
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>Default</string>
|
<string>Default</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.device-information.user-assigned-device-name</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.device.audio-input</key>
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
|||||||
114
pubspec.lock
114
pubspec.lock
@@ -285,10 +285,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.10.1"
|
version: "4.11.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -333,10 +333,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: croppy
|
name: croppy
|
||||||
sha256: "2a69059d9ec007b79d6a494854094b2e3c0a4f7ed609cf55a4805c9de9ec171d"
|
sha256: ca70a77cd5a981172d69382a7b43629d15d6c868475b2bbb45efce32cfc58b86
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.6"
|
version: "1.4.0"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -401,6 +401,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0+7.7.0"
|
version: "1.0.0+7.7.0"
|
||||||
|
dart_ipc:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dart_ipc
|
||||||
|
sha256: "6cad558cda5304017c1f581df4c96fd4f8e4ee212aae7bfa4357716236faa9ba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -413,10 +421,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_webrtc
|
name: dart_webrtc
|
||||||
sha256: "3bfa069a8b14a53ba506f6dd529e9b88c878ba0cc238f311051a39bf1e53d075"
|
sha256: "51bcda4ba5d7dd9e65a309244ce3ac0b58025e6e1f6d7442cee4cd02134ef65f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.3+hotfix.5"
|
version: "1.6.0"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -546,7 +554,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.3"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||||
@@ -565,10 +573,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
|
sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.2"
|
version: "10.3.3"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -709,10 +717,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fl_chart
|
name: fl_chart
|
||||||
sha256: d3f82f4a38e33ba23d05a08ff304d7d8b22d2a59a5503f20bd802966e915db89
|
sha256: "7ca9a40f4eb85949190e54087be8b4d6ac09dc4c54238d782a34cf1f7c011de9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -906,10 +914,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: a9966c850de5e445331b854fa42df96a8020066d67f125a5964cbc6556643f68
|
sha256: "7ed76be64e8a7d01dfdf250b8434618e2a028c9dfa2a3c41dc9b531d4b3fc8a5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "19.4.1"
|
version: "19.4.2"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -930,10 +938,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications_windows
|
name: flutter_local_notifications_windows
|
||||||
sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98
|
sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.3"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -1076,10 +1084,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_timezone
|
name: flutter_timezone
|
||||||
sha256: "13b2109ad75651faced4831bf262e32559e44aa549426eab8a597610d385d934"
|
sha256: ccad42fbb5d01d51d3eb281cc4428fca556cc4063c52bd9fa40f80cd93b8e649
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.1"
|
version: "5.0.0"
|
||||||
flutter_typeahead:
|
flutter_typeahead:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1105,10 +1113,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: "945d0a38b90fbca8257eadb167d8fb9fa7075d9a1939fd2953c10054454d1de2"
|
sha256: "16ca9e30d428bae3dd32933e875c9f67c5843d1fa726c37cf1fc479eb9294549"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
font_awesome_flutter:
|
font_awesome_flutter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1177,10 +1185,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: eb059dfe59f08546e9787f895bd01652076f996bcbf485a8609ef990419ad227
|
sha256: b1488741c9ce37b72e026377c69a59c47378493156fc38efb5a54f6def3f92a3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "16.2.1"
|
version: "16.2.2"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1281,10 +1289,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
|
sha256: "8dfe08ea7fcf7467dbaf6889e72eebd5e0d6711caae201fdac780eb45232cd02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+1"
|
version: "0.8.13+3"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1393,10 +1401,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.1"
|
version: "11.0.2"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1433,10 +1441,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: livekit_client
|
name: livekit_client
|
||||||
sha256: "011affc0fca22b2f9b0e8827219dad9948f84f2bf057980693de13039de904c7"
|
sha256: "4c1663c1e6ac20a743d9a46c7bc71f17e1949db99d245750c68661d554e30cd2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0+hotfix.3"
|
version: "2.5.1"
|
||||||
local_auth:
|
local_auth:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1449,18 +1457,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: local_auth_android
|
name: local_auth_android
|
||||||
sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3"
|
sha256: "1ee0e63fb8b5c6fa286796b5fb1570d256857c2f4a262127e728b36b80a570cf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.52"
|
version: "1.0.53"
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: local_auth_darwin
|
name: local_auth_darwin
|
||||||
sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055"
|
sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.0"
|
version: "1.6.1"
|
||||||
local_auth_platform_interface:
|
local_auth_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1537,10 +1545,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: material_symbols_icons
|
name: material_symbols_icons
|
||||||
sha256: "2cfd19bf1c3016b0de7298eb3d3444fcb6ef093d934deb870ceb946af89cfa58"
|
sha256: "9e2042673fda5dda0b77e262220b3c34cac5806a3833da85522e41bb27fbf6c0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2872.0"
|
version: "4.2873.0"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1697,10 +1705,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
|
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.1"
|
version: "9.0.0"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1873,10 +1881,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pool
|
name: pool
|
||||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.2"
|
||||||
posix:
|
posix:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1969,10 +1977,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_android
|
name: record_android
|
||||||
sha256: "8361a791c9a3fa5c065f0b8b5adb10f12531f8538c86b19474cf7b56ea80d426"
|
sha256: "854627cd78d8d66190377f98477eee06ca96ab7c9f2e662700daf33dbf7e6673"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.2"
|
||||||
record_ios:
|
record_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2137,10 +2145,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1
|
sha256: "3424e9d5c22fd7f7590254ba09465febd6f8827c8b19a44350de4ac31d92d3a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.1.0"
|
version: "12.0.0"
|
||||||
share_plus_platform_interface:
|
share_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2576,10 +2584,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7"
|
sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.18"
|
version: "6.3.22"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2704,18 +2712,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: wakelock_plus
|
name: wakelock_plus
|
||||||
sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678
|
sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.4.0"
|
||||||
wakelock_plus_platform_interface:
|
wakelock_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: wakelock_plus_platform_interface
|
name: wakelock_plus_platform_interface
|
||||||
sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207
|
sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.3"
|
version: "1.3.0"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2765,7 +2773,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
||||||
@@ -2780,6 +2788,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
windows_notification:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: windows_notification
|
||||||
|
sha256: be3e650874615f315402c9b9f3656e29af156709c4b5cc272cb4ca0ab7ba94a8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2806,4 +2822,4 @@ packages:
|
|||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.0 <4.0.0"
|
dart: ">=3.9.0 <4.0.0"
|
||||||
flutter: ">=3.32.0"
|
flutter: ">=3.35.0"
|
||||||
|
|||||||
30
pubspec.yaml
30
pubspec.yaml
@@ -39,7 +39,7 @@ dependencies:
|
|||||||
flutter_hooks: ^0.21.3+1
|
flutter_hooks: ^0.21.3+1
|
||||||
hooks_riverpod: ^2.6.1
|
hooks_riverpod: ^2.6.1
|
||||||
bitsdojo_window: ^0.1.6
|
bitsdojo_window: ^0.1.6
|
||||||
go_router: ^16.2.1
|
go_router: ^16.2.2
|
||||||
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
|
||||||
@@ -67,29 +67,29 @@ dependencies:
|
|||||||
easy_localization: ^3.0.8
|
easy_localization: ^3.0.8
|
||||||
flutter_inappwebview: ^6.1.5
|
flutter_inappwebview: ^6.1.5
|
||||||
animations: ^2.0.11
|
animations: ^2.0.11
|
||||||
package_info_plus: ^8.3.1
|
package_info_plus: ^9.0.0
|
||||||
device_info_plus: ^11.5.0
|
device_info_plus: ^11.5.0
|
||||||
tus_client_dart:
|
tus_client_dart:
|
||||||
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.2.0
|
image_picker: ^1.2.0
|
||||||
file_picker: ^10.3.2
|
file_picker: ^10.3.3
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
image_picker_platform_interface: ^2.11.0
|
image_picker_platform_interface: ^2.11.0
|
||||||
image_picker_android: ^0.8.13+1
|
image_picker_android: ^0.8.13+3
|
||||||
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.1
|
firebase_messaging: ^16.0.1
|
||||||
flutter_udid: ^4.0.0
|
flutter_udid: ^4.0.0
|
||||||
firebase_core: ^4.1.0
|
firebase_core: ^4.1.0
|
||||||
web_socket_channel: ^3.0.3
|
web_socket_channel: ^3.0.3
|
||||||
material_symbols_icons: ^4.2872.0
|
material_symbols_icons: ^4.2873.0
|
||||||
drift: ^2.28.1
|
drift: ^2.28.1
|
||||||
drift_flutter: ^0.2.6
|
drift_flutter: ^0.2.6
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
markdown_editor_plus: ^0.2.15
|
markdown_editor_plus: ^0.2.15
|
||||||
croppy: ^1.3.6
|
croppy: ^1.4.0
|
||||||
table_calendar: ^3.2.0
|
table_calendar: ^3.2.0
|
||||||
relative_time: ^5.0.0
|
relative_time: ^5.0.0
|
||||||
dropdown_button2: ^2.3.9
|
dropdown_button2: ^2.3.9
|
||||||
@@ -103,7 +103,7 @@ dependencies:
|
|||||||
gal: ^2.3.2
|
gal: ^2.3.2
|
||||||
dismissible_page: ^1.0.2
|
dismissible_page: ^1.0.2
|
||||||
super_sliver_list: ^0.4.1
|
super_sliver_list: ^0.4.1
|
||||||
livekit_client: ^2.5.0+hotfix.3
|
livekit_client: ^2.5.1
|
||||||
pasteboard: ^0.4.0
|
pasteboard: ^0.4.0
|
||||||
flutter_colorpicker: ^1.1.0
|
flutter_colorpicker: ^1.1.0
|
||||||
record: ^6.1.1
|
record: ^6.1.1
|
||||||
@@ -112,15 +112,15 @@ dependencies:
|
|||||||
palette_generator: ^0.3.3+7
|
palette_generator: ^0.3.3+7
|
||||||
flutter_popup_card: ^0.0.6
|
flutter_popup_card: ^0.0.6
|
||||||
timezone: ^0.10.1
|
timezone: ^0.10.1
|
||||||
flutter_timezone: ^4.1.1
|
flutter_timezone: ^5.0.0
|
||||||
fl_chart: ^1.1.0
|
fl_chart: ^1.1.1
|
||||||
sign_in_with_apple: ^7.0.1
|
sign_in_with_apple: ^7.0.1
|
||||||
flutter_svg: ^2.2.1
|
flutter_svg: ^2.2.1
|
||||||
native_exif: ^0.6.2
|
native_exif: ^0.6.2
|
||||||
local_auth: ^2.3.0
|
local_auth: ^2.3.0
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
flutter_math_fork: ^0.7.4
|
flutter_math_fork: ^0.7.4
|
||||||
share_plus: ^11.1.0
|
share_plus: ^12.0.0
|
||||||
receive_sharing_intent: ^1.8.1
|
receive_sharing_intent: ^1.8.1
|
||||||
top_snackbar_flutter: ^3.3.0
|
top_snackbar_flutter: ^3.3.0
|
||||||
textfield_tags:
|
textfield_tags:
|
||||||
@@ -141,12 +141,16 @@ dependencies:
|
|||||||
flutter_card_swiper: ^7.0.2
|
flutter_card_swiper: ^7.0.2
|
||||||
file_saver: ^0.3.1
|
file_saver: ^0.3.1
|
||||||
tray_manager: ^0.5.1
|
tray_manager: ^0.5.1
|
||||||
flutter_webrtc: ^1.1.0
|
flutter_webrtc: ^1.2.0
|
||||||
flutter_local_notifications: ^19.4.1
|
flutter_local_notifications: ^19.4.2
|
||||||
wakelock_plus: ^1.3.2
|
wakelock_plus: ^1.4.0
|
||||||
slide_countdown: ^2.0.2
|
slide_countdown: ^2.0.2
|
||||||
shelf: ^1.4.2
|
shelf: ^1.4.2
|
||||||
shelf_web_socket: ^3.0.0
|
shelf_web_socket: ^3.0.0
|
||||||
|
windows_notification: ^1.3.0
|
||||||
|
win32: ^5.14.0
|
||||||
|
ffi: ^2.1.4
|
||||||
|
dart_ipc: ^1.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
; ==================================================
|
; ==================================================
|
||||||
#define AppVersion "3.2.0"
|
#define AppVersion "3.2.0"
|
||||||
#define BuildNumber "124"
|
#define BuildNumber "132"
|
||||||
; ==================================================
|
; ==================================================
|
||||||
|
|
||||||
#define FullVersion AppVersion + "." + BuildNumber
|
#define FullVersion AppVersion + "." + BuildNumber
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
||||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||||
|
#include <dart_ipc/dart_ipc_plugin_c_api.h>
|
||||||
#include <file_saver/file_saver_plugin.h>
|
#include <file_saver/file_saver_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
@@ -31,12 +32,15 @@
|
|||||||
#include <tray_manager/tray_manager_plugin.h>
|
#include <tray_manager/tray_manager_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <volume_controller/volume_controller_plugin_c_api.h>
|
#include <volume_controller/volume_controller_plugin_c_api.h>
|
||||||
|
#include <windows_notification/windows_notification_plugin_c_api.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
BitsdojoWindowPluginRegisterWithRegistrar(
|
BitsdojoWindowPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
||||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
|
DartIpcPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("DartIpcPluginCApi"));
|
||||||
FileSaverPluginRegisterWithRegistrar(
|
FileSaverPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSaverPlugin"));
|
registry->GetRegistrarForPlugin("FileSaverPlugin"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
@@ -83,4 +87,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
VolumeControllerPluginCApiRegisterWithRegistrar(
|
VolumeControllerPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("VolumeControllerPluginCApi"));
|
registry->GetRegistrarForPlugin("VolumeControllerPluginCApi"));
|
||||||
|
WindowsNotificationPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("WindowsNotificationPluginCApi"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
bitsdojo_window_windows
|
bitsdojo_window_windows
|
||||||
connectivity_plus
|
connectivity_plus
|
||||||
|
dart_ipc
|
||||||
file_saver
|
file_saver
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
firebase_core
|
firebase_core
|
||||||
@@ -28,6 +29,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
tray_manager
|
tray_manager
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
volume_controller
|
volume_controller
|
||||||
|
windows_notification
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
Reference in New Issue
Block a user