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; | ||||||
| @@ -570,9 +569,11 @@ class MessagesNotifier extends _$MessagesNotifier { | |||||||
|   if (_searchQuery == null || _searchQuery!.isEmpty) { |   if (_searchQuery == null || _searchQuery!.isEmpty) { | ||||||
|     syncMessages(); |     syncMessages(); | ||||||
|   } |   } | ||||||
|     final messages = await _getCachedMessages(offset: 0, take: 100); |  | ||||||
|     _currentPage = 0; |   final messages = await _getCachedMessages(offset: 0, take: _pageSize); | ||||||
|  |  | ||||||
|   _hasMore = messages.length == _pageSize; |   _hasMore = messages.length == _pageSize; | ||||||
|  |  | ||||||
|   state = AsyncValue.data(messages); |   state = AsyncValue.data(messages); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -582,11 +583,9 @@ class MessagesNotifier extends _$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,7 +602,6 @@ class MessagesNotifier extends _$MessagesNotifier { | |||||||
|       stackTrace: stackTrace, |       stackTrace: stackTrace, | ||||||
|     ); |     ); | ||||||
|     showErrorAlert(err); |     showErrorAlert(err); | ||||||
|       _currentPage--; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 if (detailedErrors) { |   } else { | ||||||
|     throw Exception("Failed to get device token for push notifications."); |     return universal_notify.subscribePushNotification( | ||||||
|   } |       apiClient, | ||||||
| } |       detailedErrors: detailedErrors, | ||||||
|  |  | ||||||
| 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