Compare commits

...

17 Commits

Author SHA1 Message Date
29574ada88 💄 New sticker marketplace card 2025-12-05 01:57:10 +08:00
7369f5d88c Better sticker picker 2025-12-05 01:35:16 +08:00
5b3c138ebe Sticker pack set icon 2025-12-05 01:25:38 +08:00
562bdf62e9 💄 Optimize list and credits 2025-12-05 01:18:24 +08:00
a73672925e 🐛 Fix dozens bugs 2025-12-05 01:03:42 +08:00
c585522c35 🐛 Fixes in new pagination list 2025-12-05 00:10:25 +08:00
6aba84e506 ⚗️ Testing out new own pagination utils 2025-12-04 23:43:35 +08:00
c6f104afc7 Keep alive of the chat summary 2025-12-04 22:35:45 +08:00
4181fd0090 🐛 Fix some bugs in new chat loading 2025-12-04 22:26:12 +08:00
84bca9601a ♻️ Move the data part out of the chat list UI 2025-12-04 22:20:03 +08:00
31b83b2d27 ♻️ Refactored the chat loading to use more local data 2025-12-04 22:10:07 +08:00
dfcb089c69 Embed images tappable 2025-12-04 01:15:13 +08:00
fe365e8c6d Logout a single session of a authorized device 2025-12-04 01:07:40 +08:00
b5262137ad 💄 Better authorized device page 2025-12-04 01:00:07 +08:00
11e93314c7 🐛 Fix entering room white screen 2025-12-03 22:19:08 +08:00
c8658bc0ca ⬆️ Upgrade cocoapod 2025-12-03 21:15:49 +08:00
b2f689693b ⬆️ Upgrade depenedcies 2025-12-03 21:06:38 +08:00
62 changed files with 2500 additions and 1896 deletions

View File

@@ -1020,6 +1020,8 @@
"uploadFile": "Upload File", "uploadFile": "Upload File",
"authDeviceChallenges": "Device Usage", "authDeviceChallenges": "Device Usage",
"authDeviceHint": "Swipe left to edit label, swipe right to logout device.", "authDeviceHint": "Swipe left to edit label, swipe right to logout device.",
"authSessionLogout": "Logout Session",
"authSessionLogoutHint": "Are you sure you want to logout this session? This will terminate this specific login session.",
"settingsMessageDisplayStyle": "Message Display Style", "settingsMessageDisplayStyle": "Message Display Style",
"auto": "Auto", "auto": "Auto",
"manual": "Manual", "manual": "Manual",
@@ -1489,5 +1491,7 @@
"accountActivationAlert": "Remember to activate your account", "accountActivationAlert": "Remember to activate your account",
"accountActivationAlertHint": "Unactivated account may leads to various of permission issues, activate your account by clicking the link we sent to your email inbox.", "accountActivationAlertHint": "Unactivated account may leads to various of permission issues, activate your account by clicking the link we sent to your email inbox.",
"accountActivationResendHint": "Didn't see it? Try click the button below to resend one. If you need to update your email while your account was unactivated, feel free to contact our customer service.", "accountActivationResendHint": "Didn't see it? Try click the button below to resend one. If you need to update your email while your account was unactivated, feel free to contact our customer service.",
"accountActivationResend": "Resend" "accountActivationResend": "Resend",
"ipAddress": "IP Address",
"noFurtherData": "No further data"
} }

View File

@@ -268,9 +268,9 @@ PODS:
- Flutter - Flutter
- record_ios (1.1.0): - record_ios (1.1.0):
- Flutter - Flutter
- SDWebImage (5.21.3): - SDWebImage (5.21.5):
- SDWebImage/Core (= 5.21.3) - SDWebImage/Core (= 5.21.5)
- SDWebImage/Core (5.21.3) - SDWebImage/Core (5.21.5)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@@ -281,25 +281,25 @@ PODS:
- sqflite_darwin (0.0.4): - sqflite_darwin (0.0.4):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (3.50.4): - sqlite3 (3.51.1):
- sqlite3/common (= 3.50.4) - sqlite3/common (= 3.51.1)
- sqlite3/common (3.50.4) - sqlite3/common (3.51.1)
- sqlite3/dbstatvtab (3.50.4): - sqlite3/dbstatvtab (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/fts5 (3.50.4): - sqlite3/fts5 (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/math (3.50.4): - sqlite3/math (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/perf-threadsafe (3.50.4): - sqlite3/perf-threadsafe (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/rtree (3.50.4): - sqlite3/rtree (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/session (3.50.4): - sqlite3/session (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3_flutter_libs (0.0.1): - sqlite3_flutter_libs (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (~> 3.50.4) - sqlite3 (~> 3.51.1)
- sqlite3/dbstatvtab - sqlite3/dbstatvtab
- sqlite3/fts5 - sqlite3/fts5
- sqlite3/math - sqlite3/math
@@ -545,13 +545,13 @@ SPEC CHECKSUMS:
protocol_handler_ios: 59f23ee71f3ec602d67902ca7f669a80957888d5 protocol_handler_ios: 59f23ee71f3ec602d67902ca7f669a80957888d5
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374 record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14 syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14

View File

@@ -358,8 +358,12 @@ class AppDatabase extends _$AppDatabase {
); );
} }
Future<void> saveChatRooms(List<SnChatRoom> rooms) async { Future<void> saveChatRooms(
List<SnChatRoom> rooms, {
bool override = false,
}) async {
await transaction(() async { await transaction(() async {
if (override) {
// 1. Identify rooms to remove // 1. Identify rooms to remove
final remoteRoomIds = rooms.map((r) => r.id).toSet(); final remoteRoomIds = rooms.map((r) => r.id).toSet();
final currentRooms = await select(chatRooms).get(); final currentRooms = await select(chatRooms).get();
@@ -369,13 +373,15 @@ class AppDatabase extends _$AppDatabase {
if (idsToRemove.isNotEmpty) { if (idsToRemove.isNotEmpty) {
final idsList = idsToRemove.toList(); final idsList = idsToRemove.toList();
// Remove messages // Remove messages
await (delete(chatMessages)..where((t) => t.roomId.isIn(idsList))).go(); await (delete(chatMessages)
..where((t) => t.roomId.isIn(idsList))).go();
// Remove members // Remove members
await (delete(chatMembers) await (delete(chatMembers)
..where((t) => t.chatRoomId.isIn(idsList))).go(); ..where((t) => t.chatRoomId.isIn(idsList))).go();
// Remove rooms // Remove rooms
await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go(); await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go();
} }
}
// 2. Upsert remote rooms // 2. Upsert remote rooms
await batch((batch) { await batch((batch) {

View File

@@ -216,20 +216,20 @@ sealed class SnAuthDevice with _$SnAuthDevice {
} }
@freezed @freezed
sealed class SnAuthDeviceWithChallenge with _$SnAuthDeviceWithChallenge { sealed class SnAuthDeviceWithSession with _$SnAuthDeviceWithSession {
const factory SnAuthDeviceWithChallenge({ const factory SnAuthDeviceWithSession({
required String id, required String id,
required String deviceId, required String deviceId,
required String deviceName, required String deviceName,
required String? deviceLabel, required String? deviceLabel,
required String accountId, required String accountId,
required int platform, required int platform,
required List<SnAuthChallenge> challenges, required List<SnAuthSession> sessions,
@Default(false) bool isCurrent, @Default(false) bool isCurrent,
}) = _SnAuthDeviceWithChallengee; }) = _SnAuthDeviceWithSessione;
factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) => factory SnAuthDeviceWithSession.fromJson(Map<String, dynamic> json) =>
_$SnAuthDeviceWithChallengeFromJson(json); _$SnAuthDeviceWithSessionFromJson(json);
} }
@freezed @freezed

View File

@@ -3068,51 +3068,51 @@ as bool,
} }
SnAuthDeviceWithChallenge _$SnAuthDeviceWithChallengeFromJson( SnAuthDeviceWithSession _$SnAuthDeviceWithSessionFromJson(
Map<String, dynamic> json Map<String, dynamic> json
) { ) {
return _SnAuthDeviceWithChallengee.fromJson( return _SnAuthDeviceWithSessione.fromJson(
json json
); );
} }
/// @nodoc /// @nodoc
mixin _$SnAuthDeviceWithChallenge { mixin _$SnAuthDeviceWithSession {
String get id; String get deviceId; String get deviceName; String? get deviceLabel; String get accountId; int get platform; List<SnAuthChallenge> get challenges; bool get isCurrent; String get id; String get deviceId; String get deviceName; String? get deviceLabel; String get accountId; int get platform; List<SnAuthSession> get sessions; bool get isCurrent;
/// Create a copy of SnAuthDeviceWithChallenge /// Create a copy of SnAuthDeviceWithSession
/// 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)
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$SnAuthDeviceWithChallengeCopyWith<SnAuthDeviceWithChallenge> get copyWith => _$SnAuthDeviceWithChallengeCopyWithImpl<SnAuthDeviceWithChallenge>(this as SnAuthDeviceWithChallenge, _$identity); $SnAuthDeviceWithSessionCopyWith<SnAuthDeviceWithSession> get copyWith => _$SnAuthDeviceWithSessionCopyWithImpl<SnAuthDeviceWithSession>(this as SnAuthDeviceWithSession, _$identity);
/// Serializes this SnAuthDeviceWithChallenge to a JSON map. /// Serializes this SnAuthDeviceWithSession to a JSON map.
Map<String, dynamic> toJson(); Map<String, dynamic> toJson();
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthDeviceWithChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other.challenges, challenges)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthDeviceWithSession&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other.sessions, sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,const DeepCollectionEquality().hash(challenges),isCurrent); int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,const DeepCollectionEquality().hash(sessions),isCurrent);
@override @override
String toString() { String toString() {
return 'SnAuthDeviceWithChallenge(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, challenges: $challenges, isCurrent: $isCurrent)'; return 'SnAuthDeviceWithSession(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)';
} }
} }
/// @nodoc /// @nodoc
abstract mixin class $SnAuthDeviceWithChallengeCopyWith<$Res> { abstract mixin class $SnAuthDeviceWithSessionCopyWith<$Res> {
factory $SnAuthDeviceWithChallengeCopyWith(SnAuthDeviceWithChallenge value, $Res Function(SnAuthDeviceWithChallenge) _then) = _$SnAuthDeviceWithChallengeCopyWithImpl; factory $SnAuthDeviceWithSessionCopyWith(SnAuthDeviceWithSession value, $Res Function(SnAuthDeviceWithSession) _then) = _$SnAuthDeviceWithSessionCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthSession> sessions, bool isCurrent
}); });
@@ -3120,16 +3120,16 @@ $Res call({
} }
/// @nodoc /// @nodoc
class _$SnAuthDeviceWithChallengeCopyWithImpl<$Res> class _$SnAuthDeviceWithSessionCopyWithImpl<$Res>
implements $SnAuthDeviceWithChallengeCopyWith<$Res> { implements $SnAuthDeviceWithSessionCopyWith<$Res> {
_$SnAuthDeviceWithChallengeCopyWithImpl(this._self, this._then); _$SnAuthDeviceWithSessionCopyWithImpl(this._self, this._then);
final SnAuthDeviceWithChallenge _self; final SnAuthDeviceWithSession _self;
final $Res Function(SnAuthDeviceWithChallenge) _then; final $Res Function(SnAuthDeviceWithSession) _then;
/// Create a copy of SnAuthDeviceWithChallenge /// Create a copy of SnAuthDeviceWithSession
/// 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? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? challenges = null,Object? isCurrent = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) {
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,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
@@ -3137,8 +3137,8 @@ as String,deviceName: null == deviceName ? _self.deviceName : deviceName // igno
as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,challenges: null == challenges ? _self.challenges : challenges // ignore: cast_nullable_to_non_nullable as int,sessions: null == sessions ? _self.sessions : sessions // ignore: cast_nullable_to_non_nullable
as List<SnAuthChallenge>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool, as bool,
)); ));
} }
@@ -3146,8 +3146,8 @@ as bool,
} }
/// Adds pattern-matching-related methods to [SnAuthDeviceWithChallenge]. /// Adds pattern-matching-related methods to [SnAuthDeviceWithSession].
extension SnAuthDeviceWithChallengePatterns on SnAuthDeviceWithChallenge { extension SnAuthDeviceWithSessionPatterns on SnAuthDeviceWithSession {
/// A variant of `map` that fallback to returning `orElse`. /// A variant of `map` that fallback to returning `orElse`.
/// ///
/// It is equivalent to doing: /// It is equivalent to doing:
@@ -3160,10 +3160,10 @@ extension SnAuthDeviceWithChallengePatterns on SnAuthDeviceWithChallenge {
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnAuthDeviceWithChallengee value)? $default,{required TResult orElse(),}){ @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnAuthDeviceWithSessione value)? $default,{required TResult orElse(),}){
final _that = this; final _that = this;
switch (_that) { switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null: case _SnAuthDeviceWithSessione() when $default != null:
return $default(_that);case _: return $default(_that);case _:
return orElse(); return orElse();
@@ -3182,10 +3182,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnAuthDeviceWithChallengee value) $default,){ @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnAuthDeviceWithSessione value) $default,){
final _that = this; final _that = this;
switch (_that) { switch (_that) {
case _SnAuthDeviceWithChallengee(): case _SnAuthDeviceWithSessione():
return $default(_that);} return $default(_that);}
} }
/// A variant of `map` that fallback to returning `null`. /// A variant of `map` that fallback to returning `null`.
@@ -3200,10 +3200,10 @@ return $default(_that);}
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnAuthDeviceWithChallengee value)? $default,){ @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnAuthDeviceWithSessione value)? $default,){
final _that = this; final _that = this;
switch (_that) { switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null: case _SnAuthDeviceWithSessione() when $default != null:
return $default(_that);case _: return $default(_that);case _:
return null; return null;
@@ -3221,10 +3221,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthSession> sessions, bool isCurrent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null: case _SnAuthDeviceWithSessione() when $default != null:
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);case _: return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.sessions,_that.isCurrent);case _:
return orElse(); return orElse();
} }
@@ -3242,10 +3242,10 @@ return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthSession> sessions, bool isCurrent) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnAuthDeviceWithChallengee(): case _SnAuthDeviceWithSessione():
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);} return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.sessions,_that.isCurrent);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///
@@ -3259,10 +3259,10 @@ return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthSession> sessions, bool isCurrent)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnAuthDeviceWithChallengee() when $default != null: case _SnAuthDeviceWithSessione() when $default != null:
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);case _: return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.sessions,_that.isCurrent);case _:
return null; return null;
} }
@@ -3273,9 +3273,9 @@ return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that
/// @nodoc /// @nodoc
@JsonSerializable() @JsonSerializable()
class _SnAuthDeviceWithChallengee implements SnAuthDeviceWithChallenge { class _SnAuthDeviceWithSessione implements SnAuthDeviceWithSession {
const _SnAuthDeviceWithChallengee({required this.id, required this.deviceId, required this.deviceName, required this.deviceLabel, required this.accountId, required this.platform, required final List<SnAuthChallenge> challenges, this.isCurrent = false}): _challenges = challenges; const _SnAuthDeviceWithSessione({required this.id, required this.deviceId, required this.deviceName, required this.deviceLabel, required this.accountId, required this.platform, required final List<SnAuthSession> sessions, this.isCurrent = false}): _sessions = sessions;
factory _SnAuthDeviceWithChallengee.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceWithChallengeeFromJson(json); factory _SnAuthDeviceWithSessione.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceWithSessioneFromJson(json);
@override final String id; @override final String id;
@override final String deviceId; @override final String deviceId;
@@ -3283,49 +3283,49 @@ class _SnAuthDeviceWithChallengee implements SnAuthDeviceWithChallenge {
@override final String? deviceLabel; @override final String? deviceLabel;
@override final String accountId; @override final String accountId;
@override final int platform; @override final int platform;
final List<SnAuthChallenge> _challenges; final List<SnAuthSession> _sessions;
@override List<SnAuthChallenge> get challenges { @override List<SnAuthSession> get sessions {
if (_challenges is EqualUnmodifiableListView) return _challenges; if (_sessions is EqualUnmodifiableListView) return _sessions;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_challenges); return EqualUnmodifiableListView(_sessions);
} }
@override@JsonKey() final bool isCurrent; @override@JsonKey() final bool isCurrent;
/// Create a copy of SnAuthDeviceWithChallenge /// Create a copy of SnAuthDeviceWithSession
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false) @override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$SnAuthDeviceWithChallengeeCopyWith<_SnAuthDeviceWithChallengee> get copyWith => __$SnAuthDeviceWithChallengeeCopyWithImpl<_SnAuthDeviceWithChallengee>(this, _$identity); _$SnAuthDeviceWithSessioneCopyWith<_SnAuthDeviceWithSessione> get copyWith => __$SnAuthDeviceWithSessioneCopyWithImpl<_SnAuthDeviceWithSessione>(this, _$identity);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$SnAuthDeviceWithChallengeeToJson(this, ); return _$SnAuthDeviceWithSessioneToJson(this, );
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthDeviceWithChallengee&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other._challenges, _challenges)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthDeviceWithSessione&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.deviceName, deviceName) || other.deviceName == deviceName)&&(identical(other.deviceLabel, deviceLabel) || other.deviceLabel == deviceLabel)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.platform, platform) || other.platform == platform)&&const DeepCollectionEquality().equals(other._sessions, _sessions)&&(identical(other.isCurrent, isCurrent) || other.isCurrent == isCurrent));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,const DeepCollectionEquality().hash(_challenges),isCurrent); int get hashCode => Object.hash(runtimeType,id,deviceId,deviceName,deviceLabel,accountId,platform,const DeepCollectionEquality().hash(_sessions),isCurrent);
@override @override
String toString() { String toString() {
return 'SnAuthDeviceWithChallenge(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, challenges: $challenges, isCurrent: $isCurrent)'; return 'SnAuthDeviceWithSession(id: $id, deviceId: $deviceId, deviceName: $deviceName, deviceLabel: $deviceLabel, accountId: $accountId, platform: $platform, sessions: $sessions, isCurrent: $isCurrent)';
} }
} }
/// @nodoc /// @nodoc
abstract mixin class _$SnAuthDeviceWithChallengeeCopyWith<$Res> implements $SnAuthDeviceWithChallengeCopyWith<$Res> { abstract mixin class _$SnAuthDeviceWithSessioneCopyWith<$Res> implements $SnAuthDeviceWithSessionCopyWith<$Res> {
factory _$SnAuthDeviceWithChallengeeCopyWith(_SnAuthDeviceWithChallengee value, $Res Function(_SnAuthDeviceWithChallengee) _then) = __$SnAuthDeviceWithChallengeeCopyWithImpl; factory _$SnAuthDeviceWithSessioneCopyWith(_SnAuthDeviceWithSessione value, $Res Function(_SnAuthDeviceWithSessione) _then) = __$SnAuthDeviceWithSessioneCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthChallenge> challenges, bool isCurrent String id, String deviceId, String deviceName, String? deviceLabel, String accountId, int platform, List<SnAuthSession> sessions, bool isCurrent
}); });
@@ -3333,25 +3333,25 @@ $Res call({
} }
/// @nodoc /// @nodoc
class __$SnAuthDeviceWithChallengeeCopyWithImpl<$Res> class __$SnAuthDeviceWithSessioneCopyWithImpl<$Res>
implements _$SnAuthDeviceWithChallengeeCopyWith<$Res> { implements _$SnAuthDeviceWithSessioneCopyWith<$Res> {
__$SnAuthDeviceWithChallengeeCopyWithImpl(this._self, this._then); __$SnAuthDeviceWithSessioneCopyWithImpl(this._self, this._then);
final _SnAuthDeviceWithChallengee _self; final _SnAuthDeviceWithSessione _self;
final $Res Function(_SnAuthDeviceWithChallengee) _then; final $Res Function(_SnAuthDeviceWithSessione) _then;
/// Create a copy of SnAuthDeviceWithChallenge /// Create a copy of SnAuthDeviceWithSession
/// 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? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? challenges = null,Object? isCurrent = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceId = null,Object? deviceName = null,Object? deviceLabel = freezed,Object? accountId = null,Object? platform = null,Object? sessions = null,Object? isCurrent = null,}) {
return _then(_SnAuthDeviceWithChallengee( return _then(_SnAuthDeviceWithSessione(
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,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable as String,deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,deviceName: null == deviceName ? _self.deviceName : deviceName // ignore: cast_nullable_to_non_nullable as String,deviceName: null == deviceName ? _self.deviceName : deviceName // ignore: cast_nullable_to_non_nullable
as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable as String,deviceLabel: freezed == deviceLabel ? _self.deviceLabel : deviceLabel // ignore: cast_nullable_to_non_nullable
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable as String,platform: null == platform ? _self.platform : platform // ignore: cast_nullable_to_non_nullable
as int,challenges: null == challenges ? _self._challenges : challenges // ignore: cast_nullable_to_non_nullable as int,sessions: null == sessions ? _self._sessions : sessions // ignore: cast_nullable_to_non_nullable
as List<SnAuthChallenge>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
as bool, as bool,
)); ));
} }

View File

@@ -367,24 +367,24 @@ Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) =>
'is_current': instance.isCurrent, 'is_current': instance.isCurrent,
}; };
_SnAuthDeviceWithChallengee _$SnAuthDeviceWithChallengeeFromJson( _SnAuthDeviceWithSessione _$SnAuthDeviceWithSessioneFromJson(
Map<String, dynamic> json, Map<String, dynamic> json,
) => _SnAuthDeviceWithChallengee( ) => _SnAuthDeviceWithSessione(
id: json['id'] as String, id: json['id'] as String,
deviceId: json['device_id'] as String, deviceId: json['device_id'] as String,
deviceName: json['device_name'] as String, deviceName: json['device_name'] as String,
deviceLabel: json['device_label'] as String?, deviceLabel: json['device_label'] as String?,
accountId: json['account_id'] as String, accountId: json['account_id'] as String,
platform: (json['platform'] as num).toInt(), platform: (json['platform'] as num).toInt(),
challenges: sessions:
(json['challenges'] as List<dynamic>) (json['sessions'] as List<dynamic>)
.map((e) => SnAuthChallenge.fromJson(e as Map<String, dynamic>)) .map((e) => SnAuthSession.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
isCurrent: json['is_current'] as bool? ?? false, isCurrent: json['is_current'] as bool? ?? false,
); );
Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson( Map<String, dynamic> _$SnAuthDeviceWithSessioneToJson(
_SnAuthDeviceWithChallengee instance, _SnAuthDeviceWithSessione instance,
) => <String, dynamic>{ ) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'device_id': instance.deviceId, 'device_id': instance.deviceId,
@@ -392,7 +392,7 @@ Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson(
'device_label': instance.deviceLabel, 'device_label': instance.deviceLabel,
'account_id': instance.accountId, 'account_id': instance.accountId,
'platform': instance.platform, 'platform': instance.platform,
'challenges': instance.challenges.map((e) => e.toJson()).toList(), 'sessions': instance.sessions.map((e) => e.toJson()).toList(),
'is_current': instance.isCurrent, 'is_current': instance.isCurrent,
}; };

View File

@@ -59,8 +59,9 @@ sealed class SnAuthSession with _$SnAuthSession {
required DateTime? expiredAt, required DateTime? expiredAt,
required List<dynamic> audiences, required List<dynamic> audiences,
required List<dynamic> scopes, required List<dynamic> scopes,
required String ipAddress, required String? ipAddress,
required String userAgent, required String? userAgent,
required GeoIpLocation? location,
required int type, required int type,
required String accountId, required String accountId,
required DateTime createdAt, required DateTime createdAt,

View File

@@ -885,7 +885,7 @@ $GeoIpLocationCopyWith<$Res>? get location {
/// @nodoc /// @nodoc
mixin _$SnAuthSession { mixin _$SnAuthSession {
String get id; String? get label; DateTime get lastGrantedAt; DateTime? get expiredAt; List<dynamic> get audiences; List<dynamic> get scopes; String get ipAddress; String get userAgent; int get type; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String? get label; DateTime get lastGrantedAt; DateTime? get expiredAt; List<dynamic> get audiences; List<dynamic> get scopes; String? get ipAddress; String? get userAgent; GeoIpLocation? get location; int get type; String get accountId; 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)
@@ -898,16 +898,16 @@ $SnAuthSessionCopyWith<SnAuthSession> get copyWith => _$SnAuthSessionCopyWithImp
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.type, type) || other.type == type)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other.audiences, audiences)&&const DeepCollectionEquality().equals(other.scopes, scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.location, location) || other.location == location)&&(identical(other.type, type) || other.type == type)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,type,accountId,createdAt,updatedAt,deletedAt); int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,const DeepCollectionEquality().hash(audiences),const DeepCollectionEquality().hash(scopes),ipAddress,userAgent,location,type,accountId,createdAt,updatedAt,deletedAt);
@override @override
String toString() { String toString() {
return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, type: $type, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, location: $location, type: $type, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@@ -918,11 +918,11 @@ 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, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, int type, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, List<dynamic> audiences, List<dynamic> scopes, String? ipAddress, String? userAgent, GeoIpLocation? location, int type, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
$GeoIpLocationCopyWith<$Res>? get location;
} }
/// @nodoc /// @nodoc
@@ -935,7 +935,7 @@ 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 = freezed,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? type = null,Object? accountId = 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? audiences = null,Object? scopes = null,Object? ipAddress = freezed,Object? userAgent = freezed,Object? location = freezed,Object? type = null,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,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
@@ -943,9 +943,10 @@ as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGran
as DateTime,expiredAt: freezed == 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?,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable as DateTime?,audiences: null == audiences ? _self.audiences : audiences // ignore: cast_nullable_to_non_nullable
as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable as List<dynamic>,scopes: null == scopes ? _self.scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable as List<dynamic>,ipAddress: freezed == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable as String?,userAgent: freezed == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as GeoIpLocation?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
@@ -953,7 +954,19 @@ as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ign
as DateTime?, as DateTime?,
)); ));
} }
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$GeoIpLocationCopyWith<$Res>? get location {
if (_self.location == null) {
return null;
}
return $GeoIpLocationCopyWith<$Res>(_self.location!, (value) {
return _then(_self.copyWith(location: value));
});
}
} }
@@ -1032,10 +1045,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, int type, 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, String? label, DateTime lastGrantedAt, DateTime? expiredAt, List<dynamic> audiences, List<dynamic> scopes, String? ipAddress, String? userAgent, GeoIpLocation? location, int type, String accountId, 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.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.type,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.location,_that.type,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse(); return orElse();
} }
@@ -1053,10 +1066,10 @@ 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, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, int type, String accountId, 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, List<dynamic> audiences, List<dynamic> scopes, String? ipAddress, String? userAgent, GeoIpLocation? location, int type, String accountId, 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.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.type,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);} return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.location,_that.type,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///
@@ -1070,10 +1083,10 @@ 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, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, int type, String accountId, 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, List<dynamic> audiences, List<dynamic> scopes, String? ipAddress, String? userAgent, GeoIpLocation? location, int type, String accountId, 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.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.type,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.audiences,_that.scopes,_that.ipAddress,_that.userAgent,_that.location,_that.type,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null; return null;
} }
@@ -1085,7 +1098,7 @@ return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.a
@JsonSerializable() @JsonSerializable()
class _SnAuthSession implements SnAuthSession { class _SnAuthSession implements SnAuthSession {
const _SnAuthSession({required this.id, required this.label, required this.lastGrantedAt, required this.expiredAt, required final List<dynamic> audiences, required final List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.type, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _audiences = audiences,_scopes = scopes; const _SnAuthSession({required this.id, required this.label, required this.lastGrantedAt, required this.expiredAt, required final List<dynamic> audiences, required final List<dynamic> scopes, required this.ipAddress, required this.userAgent, required this.location, required this.type, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _audiences = audiences,_scopes = scopes;
factory _SnAuthSession.fromJson(Map<String, dynamic> json) => _$SnAuthSessionFromJson(json); factory _SnAuthSession.fromJson(Map<String, dynamic> json) => _$SnAuthSessionFromJson(json);
@override final String id; @override final String id;
@@ -1106,8 +1119,9 @@ class _SnAuthSession implements SnAuthSession {
return EqualUnmodifiableListView(_scopes); return EqualUnmodifiableListView(_scopes);
} }
@override final String ipAddress; @override final String? ipAddress;
@override final String userAgent; @override final String? userAgent;
@override final GeoIpLocation? location;
@override final int type; @override final int type;
@override final String accountId; @override final String accountId;
@override final DateTime createdAt; @override final DateTime createdAt;
@@ -1127,16 +1141,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.type, type) || other.type == type)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAuthSession&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.lastGrantedAt, lastGrantedAt) || other.lastGrantedAt == lastGrantedAt)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&const DeepCollectionEquality().equals(other._audiences, _audiences)&&const DeepCollectionEquality().equals(other._scopes, _scopes)&&(identical(other.ipAddress, ipAddress) || other.ipAddress == ipAddress)&&(identical(other.userAgent, userAgent) || other.userAgent == userAgent)&&(identical(other.location, location) || other.location == location)&&(identical(other.type, type) || other.type == type)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,type,accountId,createdAt,updatedAt,deletedAt); int get hashCode => Object.hash(runtimeType,id,label,lastGrantedAt,expiredAt,const DeepCollectionEquality().hash(_audiences),const DeepCollectionEquality().hash(_scopes),ipAddress,userAgent,location,type,accountId,createdAt,updatedAt,deletedAt);
@override @override
String toString() { String toString() {
return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, type: $type, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAuthSession(id: $id, label: $label, lastGrantedAt: $lastGrantedAt, expiredAt: $expiredAt, audiences: $audiences, scopes: $scopes, ipAddress: $ipAddress, userAgent: $userAgent, location: $location, type: $type, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@@ -1147,11 +1161,11 @@ 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, List<dynamic> audiences, List<dynamic> scopes, String ipAddress, String userAgent, int type, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String? label, DateTime lastGrantedAt, DateTime? expiredAt, List<dynamic> audiences, List<dynamic> scopes, String? ipAddress, String? userAgent, GeoIpLocation? location, int type, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
@override $GeoIpLocationCopyWith<$Res>? get location;
} }
/// @nodoc /// @nodoc
@@ -1164,7 +1178,7 @@ 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 = freezed,Object? audiences = null,Object? scopes = null,Object? ipAddress = null,Object? userAgent = null,Object? type = null,Object? accountId = 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? audiences = null,Object? scopes = null,Object? ipAddress = freezed,Object? userAgent = freezed,Object? location = freezed,Object? type = null,Object? accountId = 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
@@ -1172,9 +1186,10 @@ as String?,lastGrantedAt: null == lastGrantedAt ? _self.lastGrantedAt : lastGran
as DateTime,expiredAt: freezed == 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?,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable as DateTime?,audiences: null == audiences ? _self._audiences : audiences // ignore: cast_nullable_to_non_nullable
as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable as List<dynamic>,scopes: null == scopes ? _self._scopes : scopes // ignore: cast_nullable_to_non_nullable
as List<dynamic>,ipAddress: null == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable as List<dynamic>,ipAddress: freezed == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
as String,userAgent: null == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable as String?,userAgent: freezed == userAgent ? _self.userAgent : userAgent // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
as GeoIpLocation?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
@@ -1183,7 +1198,19 @@ as DateTime?,
)); ));
} }
/// Create a copy of SnAuthSession
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$GeoIpLocationCopyWith<$Res>? get location {
if (_self.location == null) {
return null;
}
return $GeoIpLocationCopyWith<$Res>(_self.location!, (value) {
return _then(_self.copyWith(location: value));
});
}
} }

View File

@@ -96,8 +96,14 @@ _SnAuthSession _$SnAuthSessionFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['expired_at'] as String), : DateTime.parse(json['expired_at'] as String),
audiences: json['audiences'] as List<dynamic>, audiences: json['audiences'] as List<dynamic>,
scopes: json['scopes'] as List<dynamic>, scopes: json['scopes'] as List<dynamic>,
ipAddress: json['ip_address'] as String, ipAddress: json['ip_address'] as String?,
userAgent: json['user_agent'] as String, userAgent: json['user_agent'] as String?,
location:
json['location'] == null
? null
: GeoIpLocation.fromJson(
json['location'] as Map<String, dynamic>,
),
type: (json['type'] as num).toInt(), type: (json['type'] as num).toInt(),
accountId: json['account_id'] as String, accountId: json['account_id'] as String,
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
@@ -118,6 +124,7 @@ Map<String, dynamic> _$SnAuthSessionToJson(_SnAuthSession instance) =>
'scopes': instance.scopes, 'scopes': instance.scopes,
'ip_address': instance.ipAddress, 'ip_address': instance.ipAddress,
'user_agent': instance.userAgent, 'user_agent': instance.userAgent,
'location': instance.location?.toJson(),
'type': instance.type, 'type': instance.type,
'account_id': instance.accountId, 'account_id': instance.accountId,
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),

View File

@@ -30,6 +30,7 @@ sealed class SnStickerPack with _$SnStickerPack {
required String description, required String description,
required String prefix, required String prefix,
required String publisherId, required String publisherId,
required SnCloudFile? icon,
required SnPublisher? publisher, required SnPublisher? publisher,
required DateTime createdAt, required DateTime createdAt,
required DateTime updatedAt, required DateTime updatedAt,

View File

@@ -335,7 +335,7 @@ $SnStickerPackCopyWith<$Res>? get pack {
/// @nodoc /// @nodoc
mixin _$SnStickerPack { mixin _$SnStickerPack {
String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnSticker> get stickers; String get id; String get name; String get description; String get prefix; String get publisherId; SnCloudFile? get icon; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnSticker> get stickers;
/// Create a copy of SnStickerPack /// Create a copy of SnStickerPack
/// 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)
@@ -348,16 +348,16 @@ $SnStickerPackCopyWith<SnStickerPack> get copyWith => _$SnStickerPackCopyWithImp
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.stickers, stickers)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.stickers, stickers));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(stickers)); int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,icon,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(stickers));
@override @override
String toString() { String toString() {
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)'; return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, icon: $icon, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
} }
@@ -368,11 +368,11 @@ abstract mixin class $SnStickerPackCopyWith<$Res> {
factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl; factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers String id, String name, String description, String prefix, String publisherId, SnCloudFile? icon, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
}); });
$SnPublisherCopyWith<$Res>? get publisher; $SnCloudFileCopyWith<$Res>? get icon;$SnPublisherCopyWith<$Res>? get publisher;
} }
/// @nodoc /// @nodoc
@@ -385,14 +385,15 @@ class _$SnStickerPackCopyWithImpl<$Res>
/// Create a copy of SnStickerPack /// Create a copy of SnStickerPack
/// 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? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? icon = freezed,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
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,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,prefix: null == prefix ? _self.prefix : prefix // ignore: cast_nullable_to_non_nullable as String,prefix: null == prefix ? _self.prefix : prefix // ignore: cast_nullable_to_non_nullable
as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable as String,icon: freezed == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
@@ -404,6 +405,18 @@ as List<SnSticker>,
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$SnCloudFileCopyWith<$Res>? get icon {
if (_self.icon == null) {
return null;
}
return $SnCloudFileCopyWith<$Res>(_self.icon!, (value) {
return _then(_self.copyWith(icon: value));
});
}/// Create a copy of SnStickerPack
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPublisherCopyWith<$Res>? get publisher { $SnPublisherCopyWith<$Res>? get publisher {
if (_self.publisher == null) { if (_self.publisher == null) {
return null; return null;
@@ -491,10 +504,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnCloudFile? icon, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _SnStickerPack() when $default != null: case _SnStickerPack() when $default != null:
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _: return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.icon,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
return orElse(); return orElse();
} }
@@ -512,10 +525,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnCloudFile? icon, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnStickerPack(): case _SnStickerPack():
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);} return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.icon,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///
@@ -529,10 +542,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnCloudFile? icon, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnStickerPack() when $default != null: case _SnStickerPack() when $default != null:
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _: return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.icon,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
return null; return null;
} }
@@ -544,7 +557,7 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
@JsonSerializable() @JsonSerializable()
class _SnStickerPack implements SnStickerPack { class _SnStickerPack implements SnStickerPack {
const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt, final List<SnSticker> stickers = const []}): _stickers = stickers; const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.icon, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt, final List<SnSticker> stickers = const []}): _stickers = stickers;
factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json); factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json);
@override final String id; @override final String id;
@@ -552,6 +565,7 @@ class _SnStickerPack implements SnStickerPack {
@override final String description; @override final String description;
@override final String prefix; @override final String prefix;
@override final String publisherId; @override final String publisherId;
@override final SnCloudFile? icon;
@override final SnPublisher? publisher; @override final SnPublisher? publisher;
@override final DateTime createdAt; @override final DateTime createdAt;
@override final DateTime updatedAt; @override final DateTime updatedAt;
@@ -577,16 +591,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._stickers, _stickers)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.icon, icon) || other.icon == icon)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._stickers, _stickers));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_stickers)); int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,icon,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_stickers));
@override @override
String toString() { String toString() {
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)'; return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, icon: $icon, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
} }
@@ -597,11 +611,11 @@ abstract mixin class _$SnStickerPackCopyWith<$Res> implements $SnStickerPackCopy
factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl; factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers String id, String name, String description, String prefix, String publisherId, SnCloudFile? icon, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
}); });
@override $SnPublisherCopyWith<$Res>? get publisher; @override $SnCloudFileCopyWith<$Res>? get icon;@override $SnPublisherCopyWith<$Res>? get publisher;
} }
/// @nodoc /// @nodoc
@@ -614,14 +628,15 @@ class __$SnStickerPackCopyWithImpl<$Res>
/// Create a copy of SnStickerPack /// Create a copy of SnStickerPack
/// 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? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? icon = freezed,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
return _then(_SnStickerPack( return _then(_SnStickerPack(
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,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String,prefix: null == prefix ? _self.prefix : prefix // ignore: cast_nullable_to_non_nullable as String,prefix: null == prefix ? _self.prefix : prefix // ignore: cast_nullable_to_non_nullable
as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable as String,icon: freezed == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as SnPublisher?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
@@ -634,6 +649,18 @@ as List<SnSticker>,
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$SnCloudFileCopyWith<$Res>? get icon {
if (_self.icon == null) {
return null;
}
return $SnCloudFileCopyWith<$Res>(_self.icon!, (value) {
return _then(_self.copyWith(icon: value));
});
}/// Create a copy of SnStickerPack
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPublisherCopyWith<$Res>? get publisher { $SnPublisherCopyWith<$Res>? get publisher {
if (_self.publisher == null) { if (_self.publisher == null) {
return null; return null;

View File

@@ -42,6 +42,10 @@ _SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) =>
description: json['description'] as String, description: json['description'] as String,
prefix: json['prefix'] as String, prefix: json['prefix'] as String,
publisherId: json['publisher_id'] as String, publisherId: json['publisher_id'] as String,
icon:
json['icon'] == null
? null
: SnCloudFile.fromJson(json['icon'] as Map<String, dynamic>),
publisher: publisher:
json['publisher'] == null json['publisher'] == null
? null ? null
@@ -66,6 +70,7 @@ Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
'description': instance.description, 'description': instance.description,
'prefix': instance.prefix, 'prefix': instance.prefix,
'publisher_id': instance.publisherId, 'publisher_id': instance.publisherId,
'icon': instance.icon?.toJson(),
'publisher': instance.publisher?.toJson(), 'publisher': instance.publisher?.toJson(),
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),

View File

@@ -0,0 +1,373 @@
import 'package:dio/dio.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/database/drift_db.dart';
import 'package:island/models/account.dart';
import 'package:island/models/chat.dart';
import 'package:island/models/file.dart';
import 'package:island/pods/database.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'chat_room.g.dart';
final isSyncingProvider = StateProvider.autoDispose<bool>((ref) => false);
final flashingMessagesProvider = StateProvider<Set<String>>((ref) => {});
@riverpod
class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
@override
Future<List<SnChatRoom>> build() async {
final db = ref.watch(databaseProvider);
try {
final localRoomsData = await db.select(db.chatRooms).get();
if (localRoomsData.isNotEmpty) {
final localRooms = await Future.wait(
localRoomsData.map((row) async {
final membersRows =
await (db.select(db.chatMembers)
..where((m) => m.chatRoomId.equals(row.id))).get();
final members =
membersRows.map((mRow) {
final account = SnAccount.fromJson(mRow.account);
return SnChatMember(
id: mRow.id,
chatRoomId: mRow.chatRoomId,
accountId: mRow.accountId,
account: account,
nick: mRow.nick,
notify: mRow.notify,
joinedAt: mRow.joinedAt,
breakUntil: mRow.breakUntil,
timeoutUntil: mRow.timeoutUntil,
status: null,
createdAt: mRow.createdAt,
updatedAt: mRow.updatedAt,
deletedAt: mRow.deletedAt,
chatRoom: null,
);
}).toList();
return SnChatRoom(
id: row.id,
name: row.name,
description: row.description,
type: row.type,
isPublic: row.isPublic!,
isCommunity: row.isCommunity!,
picture:
row.picture != null
? SnCloudFile.fromJson(row.picture!)
: null,
background:
row.background != null
? SnCloudFile.fromJson(row.background!)
: null,
realmId: row.realmId,
accountId: row.accountId,
realm: null,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
members: members,
);
}),
);
// Background sync
Future(() async {
try {
final client = ref.read(apiClientProvider);
final resp = await client.get('/sphere/chat');
final remoteRooms =
resp.data
.map((e) => SnChatRoom.fromJson(e))
.cast<SnChatRoom>()
.toList();
await db.saveChatRooms(remoteRooms, override: true);
// Update state with fresh data
state = AsyncData(await _buildRoomsFromDb(db));
} catch (_) {}
}).ignore();
return localRooms;
}
} catch (_) {}
// Fallback to API
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat');
final rooms =
resp.data
.map((e) => SnChatRoom.fromJson(e))
.cast<SnChatRoom>()
.toList();
await db.saveChatRooms(rooms, override: true);
return rooms;
}
Future<List<SnChatRoom>> _buildRoomsFromDb(AppDatabase db) async {
final localRoomsData = await db.select(db.chatRooms).get();
return Future.wait(
localRoomsData.map((row) async {
final membersRows =
await (db.select(db.chatMembers)
..where((m) => m.chatRoomId.equals(row.id))).get();
final members =
membersRows.map((mRow) {
final account = SnAccount.fromJson(mRow.account);
return SnChatMember(
id: mRow.id,
chatRoomId: mRow.chatRoomId,
accountId: mRow.accountId,
account: account,
nick: mRow.nick,
notify: mRow.notify,
joinedAt: mRow.joinedAt,
breakUntil: mRow.breakUntil,
timeoutUntil: mRow.timeoutUntil,
status: null,
createdAt: mRow.createdAt,
updatedAt: mRow.updatedAt,
deletedAt: mRow.deletedAt,
chatRoom: null,
);
}).toList();
return SnChatRoom(
id: row.id,
name: row.name,
description: row.description,
type: row.type,
isPublic: row.isPublic!,
isCommunity: row.isCommunity!,
picture:
row.picture != null ? SnCloudFile.fromJson(row.picture!) : null,
background:
row.background != null
? SnCloudFile.fromJson(row.background!)
: null,
realmId: row.realmId,
accountId: row.accountId,
realm: null,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
members: members,
);
}),
);
}
}
@riverpod
class ChatRoomNotifier extends _$ChatRoomNotifier {
@override
Future<SnChatRoom?> build(String? identifier) async {
if (identifier == null) return null;
final db = ref.watch(databaseProvider);
try {
// Try to get from local database first
final localRoomData =
await (db.select(db.chatRooms)
..where((r) => r.id.equals(identifier))).getSingleOrNull();
if (localRoomData != null) {
// Fetch members for this room
final membersRows =
await (db.select(db.chatMembers)
..where((m) => m.chatRoomId.equals(localRoomData.id))).get();
final members =
membersRows.map((mRow) {
final account = SnAccount.fromJson(mRow.account);
return SnChatMember(
id: mRow.id,
chatRoomId: mRow.chatRoomId,
accountId: mRow.accountId,
account: account,
nick: mRow.nick,
notify: mRow.notify,
joinedAt: mRow.joinedAt,
breakUntil: mRow.breakUntil,
timeoutUntil: mRow.timeoutUntil,
status: null,
createdAt: mRow.createdAt,
updatedAt: mRow.updatedAt,
deletedAt: mRow.deletedAt,
chatRoom: null,
);
}).toList();
final localRoom = SnChatRoom(
id: localRoomData.id,
name: localRoomData.name,
description: localRoomData.description,
type: localRoomData.type,
isPublic: localRoomData.isPublic!,
isCommunity: localRoomData.isCommunity!,
picture:
localRoomData.picture != null
? SnCloudFile.fromJson(localRoomData.picture!)
: null,
background:
localRoomData.background != null
? SnCloudFile.fromJson(localRoomData.background!)
: null,
realmId: localRoomData.realmId,
accountId: localRoomData.accountId,
realm: null,
createdAt: localRoomData.createdAt,
updatedAt: localRoomData.updatedAt,
deletedAt: localRoomData.deletedAt,
members: members,
);
// Background sync
Future(() async {
try {
final client = ref.read(apiClientProvider);
final resp = await client.get('/sphere/chat/$identifier');
final remoteRoom = SnChatRoom.fromJson(resp.data);
// Update state with fresh data directly without saving to DB
// DB will be updated by ChatRoomJoinedNotifier's full sync
state = AsyncData(remoteRoom);
} catch (_) {}
}).ignore();
return localRoom;
}
} catch (_) {}
// Fallback to API
try {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/$identifier');
final room = SnChatRoom.fromJson(resp.data);
await db.saveChatRooms([room]);
return room;
} catch (err) {
if (err is DioException && err.response?.statusCode == 404) {
return null; // Chat room not found
}
rethrow; // Rethrow other errors
}
}
}
@riverpod
class ChatRoomIdentityNotifier extends _$ChatRoomIdentityNotifier {
@override
Future<SnChatMember?> build(String? identifier) async {
if (identifier == null) return null;
final db = ref.watch(databaseProvider);
final userInfo = ref.watch(userInfoProvider);
try {
// Try to get from local database first
if (userInfo.value != null) {
final localMemberData =
await (db.select(db.chatMembers)
..where((m) => m.chatRoomId.equals(identifier))
..where((m) => m.accountId.equals(userInfo.value!.id)))
.getSingleOrNull();
if (localMemberData != null) {
final account = SnAccount.fromJson(localMemberData.account);
final localMember = SnChatMember(
id: localMemberData.id,
chatRoomId: localMemberData.chatRoomId,
accountId: localMemberData.accountId,
account: account,
nick: localMemberData.nick,
notify: localMemberData.notify,
joinedAt: localMemberData.joinedAt,
breakUntil: localMemberData.breakUntil,
timeoutUntil: localMemberData.timeoutUntil,
status: null,
createdAt: localMemberData.createdAt,
updatedAt: localMemberData.updatedAt,
deletedAt: localMemberData.deletedAt,
chatRoom: null,
);
// Background sync
Future(() async {
try {
final client = ref.read(apiClientProvider);
final resp = await client.get(
'/sphere/chat/$identifier/members/me',
);
final remoteMember = SnChatMember.fromJson(resp.data);
await db.saveMember(remoteMember);
// Update state with fresh data
if (userInfo.value != null) {
state = AsyncData(
await _buildMemberFromDb(db, identifier, userInfo.value!.id),
);
}
} catch (_) {}
}).ignore();
return localMember;
}
}
} catch (_) {}
// Fallback to API
try {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/$identifier/members/me');
final member = SnChatMember.fromJson(resp.data);
await db.saveMember(member);
return member;
} catch (err) {
if (err is DioException && err.response?.statusCode == 404) {
return null; // Chat member not found
}
rethrow; // Rethrow other errors
}
}
Future<SnChatMember?> _buildMemberFromDb(
AppDatabase db,
String identifier,
String accountId,
) async {
final localMemberData =
await (db.select(db.chatMembers)
..where((m) => m.chatRoomId.equals(identifier))
..where((m) => m.accountId.equals(accountId)))
.getSingleOrNull();
if (localMemberData == null) return null;
final account = SnAccount.fromJson(localMemberData.account);
return SnChatMember(
id: localMemberData.id,
chatRoomId: localMemberData.chatRoomId,
accountId: localMemberData.accountId,
account: account,
nick: localMemberData.nick,
notify: localMemberData.notify,
joinedAt: localMemberData.joinedAt,
breakUntil: localMemberData.breakUntil,
timeoutUntil: localMemberData.timeoutUntil,
status: null,
createdAt: localMemberData.createdAt,
updatedAt: localMemberData.updatedAt,
deletedAt: localMemberData.deletedAt,
chatRoom: null,
);
}
}
@riverpod
Future<List<SnChatMember>> chatroomInvites(Ref ref) async {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/invites');
return resp.data
.map((e) => SnChatMember.fromJson(e))
.cast<SnChatMember>()
.toList();
}

View File

@@ -0,0 +1,355 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_room.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$chatroomInvitesHash() => r'5cd6391b09c5517ede19bacce43b45c8d71dd087';
/// See also [chatroomInvites].
@ProviderFor(chatroomInvites)
final chatroomInvitesProvider =
AutoDisposeFutureProvider<List<SnChatMember>>.internal(
chatroomInvites,
name: r'chatroomInvitesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomInvitesHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ChatroomInvitesRef = AutoDisposeFutureProviderRef<List<SnChatMember>>;
String _$chatRoomJoinedNotifierHash() =>
r'c8092225ba0d9c08b2b5bca6f800f1877303b4ff';
/// See also [ChatRoomJoinedNotifier].
@ProviderFor(ChatRoomJoinedNotifier)
final chatRoomJoinedNotifierProvider = AutoDisposeAsyncNotifierProvider<
ChatRoomJoinedNotifier,
List<SnChatRoom>
>.internal(
ChatRoomJoinedNotifier.new,
name: r'chatRoomJoinedNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatRoomJoinedNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ChatRoomJoinedNotifier = AutoDisposeAsyncNotifier<List<SnChatRoom>>;
String _$chatRoomNotifierHash() => r'1e6391e2ab4eeb114fa001aaa6b06ab2bd646f38';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$ChatRoomNotifier
extends BuildlessAutoDisposeAsyncNotifier<SnChatRoom?> {
late final String? identifier;
FutureOr<SnChatRoom?> build(String? identifier);
}
/// See also [ChatRoomNotifier].
@ProviderFor(ChatRoomNotifier)
const chatRoomNotifierProvider = ChatRoomNotifierFamily();
/// See also [ChatRoomNotifier].
class ChatRoomNotifierFamily extends Family<AsyncValue<SnChatRoom?>> {
/// See also [ChatRoomNotifier].
const ChatRoomNotifierFamily();
/// See also [ChatRoomNotifier].
ChatRoomNotifierProvider call(String? identifier) {
return ChatRoomNotifierProvider(identifier);
}
@override
ChatRoomNotifierProvider getProviderOverride(
covariant ChatRoomNotifierProvider provider,
) {
return call(provider.identifier);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'chatRoomNotifierProvider';
}
/// See also [ChatRoomNotifier].
class ChatRoomNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<ChatRoomNotifier, SnChatRoom?> {
/// See also [ChatRoomNotifier].
ChatRoomNotifierProvider(String? identifier)
: this._internal(
() => ChatRoomNotifier()..identifier = identifier,
from: chatRoomNotifierProvider,
name: r'chatRoomNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatRoomNotifierHash,
dependencies: ChatRoomNotifierFamily._dependencies,
allTransitiveDependencies:
ChatRoomNotifierFamily._allTransitiveDependencies,
identifier: identifier,
);
ChatRoomNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.identifier,
}) : super.internal();
final String? identifier;
@override
FutureOr<SnChatRoom?> runNotifierBuild(covariant ChatRoomNotifier notifier) {
return notifier.build(identifier);
}
@override
Override overrideWith(ChatRoomNotifier Function() create) {
return ProviderOverride(
origin: this,
override: ChatRoomNotifierProvider._internal(
() => create()..identifier = identifier,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
identifier: identifier,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<ChatRoomNotifier, SnChatRoom?>
createElement() {
return _ChatRoomNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ChatRoomNotifierProvider && other.identifier == identifier;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, identifier.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ChatRoomNotifierRef on AutoDisposeAsyncNotifierProviderRef<SnChatRoom?> {
/// The parameter `identifier` of this provider.
String? get identifier;
}
class _ChatRoomNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<ChatRoomNotifier, SnChatRoom?>
with ChatRoomNotifierRef {
_ChatRoomNotifierProviderElement(super.provider);
@override
String? get identifier => (origin as ChatRoomNotifierProvider).identifier;
}
String _$chatRoomIdentityNotifierHash() =>
r'27c17d55366d39be81d7209837e5c01f80a68a24';
abstract class _$ChatRoomIdentityNotifier
extends BuildlessAutoDisposeAsyncNotifier<SnChatMember?> {
late final String? identifier;
FutureOr<SnChatMember?> build(String? identifier);
}
/// See also [ChatRoomIdentityNotifier].
@ProviderFor(ChatRoomIdentityNotifier)
const chatRoomIdentityNotifierProvider = ChatRoomIdentityNotifierFamily();
/// See also [ChatRoomIdentityNotifier].
class ChatRoomIdentityNotifierFamily extends Family<AsyncValue<SnChatMember?>> {
/// See also [ChatRoomIdentityNotifier].
const ChatRoomIdentityNotifierFamily();
/// See also [ChatRoomIdentityNotifier].
ChatRoomIdentityNotifierProvider call(String? identifier) {
return ChatRoomIdentityNotifierProvider(identifier);
}
@override
ChatRoomIdentityNotifierProvider getProviderOverride(
covariant ChatRoomIdentityNotifierProvider provider,
) {
return call(provider.identifier);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'chatRoomIdentityNotifierProvider';
}
/// See also [ChatRoomIdentityNotifier].
class ChatRoomIdentityNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
ChatRoomIdentityNotifier,
SnChatMember?
> {
/// See also [ChatRoomIdentityNotifier].
ChatRoomIdentityNotifierProvider(String? identifier)
: this._internal(
() => ChatRoomIdentityNotifier()..identifier = identifier,
from: chatRoomIdentityNotifierProvider,
name: r'chatRoomIdentityNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatRoomIdentityNotifierHash,
dependencies: ChatRoomIdentityNotifierFamily._dependencies,
allTransitiveDependencies:
ChatRoomIdentityNotifierFamily._allTransitiveDependencies,
identifier: identifier,
);
ChatRoomIdentityNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.identifier,
}) : super.internal();
final String? identifier;
@override
FutureOr<SnChatMember?> runNotifierBuild(
covariant ChatRoomIdentityNotifier notifier,
) {
return notifier.build(identifier);
}
@override
Override overrideWith(ChatRoomIdentityNotifier Function() create) {
return ProviderOverride(
origin: this,
override: ChatRoomIdentityNotifierProvider._internal(
() => create()..identifier = identifier,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
identifier: identifier,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
ChatRoomIdentityNotifier,
SnChatMember?
>
createElement() {
return _ChatRoomIdentityNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ChatRoomIdentityNotifierProvider &&
other.identifier == identifier;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, identifier.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ChatRoomIdentityNotifierRef
on AutoDisposeAsyncNotifierProviderRef<SnChatMember?> {
/// The parameter `identifier` of this provider.
String? get identifier;
}
class _ChatRoomIdentityNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
ChatRoomIdentityNotifier,
SnChatMember?
>
with ChatRoomIdentityNotifierRef {
_ChatRoomIdentityNotifierProviderElement(super.provider);
@override
String? get identifier =>
(origin as ChatRoomIdentityNotifierProvider).identifier;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -1,5 +0,0 @@
import "package:hooks_riverpod/hooks_riverpod.dart";
final isSyncingProvider = StateProvider.autoDispose<bool>((ref) => false);
final flashingMessagesProvider = StateProvider<Set<String>>((ref) => {});

View File

@@ -3,10 +3,10 @@ import "dart:convert";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:island/models/chat.dart"; import "package:island/models/chat.dart";
import "package:island/pods/chat/chat_room.dart";
import "package:island/pods/lifecycle.dart"; import "package:island/pods/lifecycle.dart";
import "package:island/pods/chat/messages_notifier.dart"; import "package:island/pods/chat/messages_notifier.dart";
import "package:island/pods/websocket.dart"; import "package:island/pods/websocket.dart";
import "package:island/screens/chat/chat.dart";
import "package:island/widgets/chat/call_button.dart"; import "package:island/widgets/chat/call_button.dart";
import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:riverpod_annotation/riverpod_annotation.dart";
@@ -16,10 +16,9 @@ final currentSubscribedChatIdProvider = StateProvider<String?>((ref) => null);
@riverpod @riverpod
class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
late final String _roomId; late SnChatRoom _chatRoom;
late final SnChatRoom _chatRoom; late SnChatMember _chatIdentity;
late final SnChatMember _chatIdentity; late MessagesNotifier _messagesNotifier;
late final MessagesNotifier _messagesNotifier;
final List<SnChatMember> _typingStatuses = []; final List<SnChatMember> _typingStatuses = [];
Timer? _typingCleanupTimer; Timer? _typingCleanupTimer;
@@ -29,10 +28,11 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
@override @override
List<SnChatMember> build(String roomId) { List<SnChatMember> build(String roomId) {
_roomId = roomId;
final ws = ref.watch(websocketProvider); final ws = ref.watch(websocketProvider);
final chatRoomAsync = ref.watch(chatroomProvider(roomId)); final chatRoomAsync = ref.watch(ChatRoomNotifierProvider(roomId));
final chatIdentityAsync = ref.watch(chatroomIdentityProvider(roomId)); final chatIdentityAsync = ref.watch(
ChatRoomIdentityNotifierProvider(roomId),
);
_messagesNotifier = ref.watch(messagesNotifierProvider(roomId).notifier); _messagesNotifier = ref.watch(messagesNotifierProvider(roomId).notifier);
if (chatRoomAsync.isLoading || chatIdentityAsync.isLoading) { if (chatRoomAsync.isLoading || chatIdentityAsync.isLoading) {
@@ -199,7 +199,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
jsonEncode( jsonEncode(
WebSocketPacket( WebSocketPacket(
type: 'messages.read', type: 'messages.read',
data: {'chat_room_id': _roomId}, data: {'chat_room_id': roomId},
endpoint: 'sphere', endpoint: 'sphere',
), ),
), ),
@@ -216,7 +216,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
jsonEncode( jsonEncode(
WebSocketPacket( WebSocketPacket(
type: 'messages.typing', type: 'messages.typing',
data: {'chat_room_id': _roomId}, data: {'chat_room_id': roomId},
endpoint: 'sphere', endpoint: 'sphere',
), ),
), ),

View File

@@ -7,7 +7,7 @@ part of 'chat_subscribe.dart';
// ************************************************************************** // **************************************************************************
String _$chatSubscribeNotifierHash() => String _$chatSubscribeNotifierHash() =>
r'c605e0c9c45df64e5ba7b65f8de9b47bde8e2b3b'; r'beec1ddf2e13f6d5af8a08c2c81eff740ae9b986';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {

View File

@@ -60,7 +60,7 @@ class ChatUnreadCountNotifier extends _$ChatUnreadCountNotifier {
} }
} }
@riverpod @Riverpod(keepAlive: true)
class ChatSummary extends _$ChatSummary { class ChatSummary extends _$ChatSummary {
@override @override
Future<Map<String, SnChatSummary>> build() async { Future<Map<String, SnChatSummary>> build() async {

View File

@@ -24,22 +24,22 @@ final chatUnreadCountNotifierProvider =
); );
typedef _$ChatUnreadCountNotifier = AutoDisposeAsyncNotifier<int>; typedef _$ChatUnreadCountNotifier = AutoDisposeAsyncNotifier<int>;
String _$chatSummaryHash() => r'8479ef53cfb0b698b800d0117d04774b6f78b3cc'; String _$chatSummaryHash() => r'78d927d40cded9d7adbc20bd6f457fdf3c852632';
/// See also [ChatSummary]. /// See also [ChatSummary].
@ProviderFor(ChatSummary) @ProviderFor(ChatSummary)
final chatSummaryProvider = AutoDisposeAsyncNotifierProvider< final chatSummaryProvider =
ChatSummary, AsyncNotifierProvider<ChatSummary, Map<String, SnChatSummary>>.internal(
Map<String, SnChatSummary>
>.internal(
ChatSummary.new, ChatSummary.new,
name: r'chatSummaryProvider', name: r'chatSummaryProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$chatSummaryHash, const bool.fromEnvironment('dart.vm.product')
? null
: _$chatSummaryHash,
dependencies: null, dependencies: null,
allTransitiveDependencies: null, allTransitiveDependencies: null,
); );
typedef _$ChatSummary = AutoDisposeAsyncNotifier<Map<String, SnChatSummary>>; typedef _$ChatSummary = AsyncNotifier<Map<String, SnChatSummary>>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -11,6 +11,7 @@ import "package:island/models/chat.dart";
import "package:island/models/file.dart"; import "package:island/models/file.dart";
import "package:island/models/poll.dart"; import "package:island/models/poll.dart";
import "package:island/models/wallet.dart"; import "package:island/models/wallet.dart";
import "package:island/pods/chat/chat_room.dart";
import "package:island/pods/database.dart"; import "package:island/pods/database.dart";
import "package:island/pods/lifecycle.dart"; import "package:island/pods/lifecycle.dart";
import "package:island/pods/network.dart"; import "package:island/pods/network.dart";
@@ -19,18 +20,16 @@ import "package:island/talker.dart";
import "package:island/widgets/alert.dart"; import "package:island/widgets/alert.dart";
import "package:riverpod_annotation/riverpod_annotation.dart"; import "package:riverpod_annotation/riverpod_annotation.dart";
import "package:uuid/uuid.dart"; import "package:uuid/uuid.dart";
import "package:island/screens/chat/chat.dart";
import "package:island/pods/chat/chat_rooms.dart";
import "package:island/screens/account/profile.dart"; import "package:island/screens/account/profile.dart";
part 'messages_notifier.g.dart'; part 'messages_notifier.g.dart';
@riverpod @riverpod
class MessagesNotifier extends _$MessagesNotifier { class MessagesNotifier extends _$MessagesNotifier {
late final Dio _apiClient; late Dio _apiClient;
late final AppDatabase _database; late AppDatabase _database;
late final SnChatRoom _room; late SnChatRoom _room;
late final SnChatMember _identity; late SnChatMember _identity;
final Map<String, LocalChatMessage> _pendingMessages = {}; final Map<String, LocalChatMessage> _pendingMessages = {};
final Map<String, Map<int, double?>> _fileUploadProgress = {}; final Map<String, Map<int, double?>> _fileUploadProgress = {};
@@ -39,7 +38,6 @@ class MessagesNotifier extends _$MessagesNotifier {
bool? _withLinks; bool? _withLinks;
bool? _withAttachments; bool? _withAttachments;
late final String _roomId;
static const int _pageSize = 20; static const int _pageSize = 20;
bool _hasMore = true; bool _hasMore = true;
bool _isSyncing = false; bool _isSyncing = false;
@@ -48,15 +46,16 @@ class MessagesNotifier extends _$MessagesNotifier {
bool _allRemoteMessagesFetched = false; bool _allRemoteMessagesFetched = false;
DateTime? _lastPauseTime; DateTime? _lastPauseTime;
late final Future<SnAccount?> Function(String) _fetchAccount; late Future<SnAccount?> Function(String) _fetchAccount;
@override @override
FutureOr<List<LocalChatMessage>> build(String roomId) async { FutureOr<List<LocalChatMessage>> build(String roomId) async {
_roomId = roomId;
_apiClient = ref.watch(apiClientProvider); _apiClient = ref.watch(apiClientProvider);
_database = ref.watch(databaseProvider); _database = ref.watch(databaseProvider);
final room = await ref.watch(chatroomProvider(roomId).future); final room = await ref.watch(ChatRoomNotifierProvider(roomId).future);
final identity = await ref.watch(chatroomIdentityProvider(roomId).future); final identity = await ref.watch(
ChatRoomIdentityNotifierProvider(roomId).future,
);
// Initialize fetch account method for corrupted data recovery // Initialize fetch account method for corrupted data recovery
_fetchAccount = (String accountId) async { _fetchAccount = (String accountId) async {
@@ -144,14 +143,14 @@ class MessagesNotifier extends _$MessagesNotifier {
if (searchQuery != null && searchQuery.isNotEmpty) { if (searchQuery != null && searchQuery.isNotEmpty) {
dbMessages = await _database.searchMessages( dbMessages = await _database.searchMessages(
_roomId, roomId,
searchQuery, searchQuery,
withAttachments: withAttachments, withAttachments: withAttachments,
fetchAccount: _fetchAccount, fetchAccount: _fetchAccount,
); );
} else { } else {
final chatMessagesFromDb = await _database.getMessagesForRoom( final chatMessagesFromDb = await _database.getMessagesForRoom(
_roomId, roomId,
offset: offset, offset: offset,
limit: take, limit: take,
); );
@@ -194,9 +193,7 @@ class MessagesNotifier extends _$MessagesNotifier {
if (offset == 0) { if (offset == 0) {
final pendingForRoom = final pendingForRoom =
_pendingMessages.values _pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
.where((msg) => msg.roomId == _roomId)
.toList();
final allMessages = [...pendingForRoom, ...uniqueMessages]; final allMessages = [...pendingForRoom, ...uniqueMessages];
_sortMessages(allMessages); // Use the helper function _sortMessages(allMessages); // Use the helper function
@@ -221,7 +218,7 @@ class MessagesNotifier extends _$MessagesNotifier {
}) async { }) async {
talker.log('Getting all messages for jump from offset $offset, take $take'); talker.log('Getting all messages for jump from offset $offset, take $take');
final chatMessagesFromDb = await _database.getMessagesForRoom( final chatMessagesFromDb = await _database.getMessagesForRoom(
_roomId, roomId,
offset: offset, offset: offset,
limit: take, limit: take,
); );
@@ -245,9 +242,7 @@ class MessagesNotifier extends _$MessagesNotifier {
if (offset == 0) { if (offset == 0) {
final pendingForRoom = final pendingForRoom =
_pendingMessages.values _pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
.where((msg) => msg.roomId == _roomId)
.toList();
final allMessages = [...pendingForRoom, ...uniqueMessages]; final allMessages = [...pendingForRoom, ...uniqueMessages];
_sortMessages(allMessages); _sortMessages(allMessages);
@@ -272,7 +267,7 @@ class MessagesNotifier extends _$MessagesNotifier {
talker.log('Fetching messages from API, offset $offset, take $take'); talker.log('Fetching messages from API, offset $offset, take $take');
if (_totalCount == null) { if (_totalCount == null) {
final response = await _apiClient.get( final response = await _apiClient.get(
'/sphere/chat/$_roomId/messages', '/sphere/chat/$roomId/messages',
queryParameters: {'offset': 0, 'take': 1}, queryParameters: {'offset': 0, 'take': 1},
); );
_totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0'); _totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0');
@@ -284,7 +279,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
final response = await _apiClient.get( final response = await _apiClient.get(
'/sphere/chat/$_roomId/messages', '/sphere/chat/$roomId/messages',
queryParameters: {'offset': offset, 'take': take}, queryParameters: {'offset': offset, 'take': take},
); );
@@ -546,7 +541,7 @@ class MessagesNotifier extends _$MessagesNotifier {
final mockMessage = SnChatMessage( final mockMessage = SnChatMessage(
id: 'pending_$nonce', id: 'pending_$nonce',
chatRoomId: _roomId, chatRoomId: roomId,
senderId: _identity.id, senderId: _identity.id,
content: content, content: content,
createdAt: DateTime.now(), createdAt: DateTime.now(),
@@ -590,8 +585,8 @@ class MessagesNotifier extends _$MessagesNotifier {
final response = await _apiClient.request( final response = await _apiClient.request(
editingTo == null editingTo == null
? '/sphere/chat/$_roomId/messages' ? '/sphere/chat/$roomId/messages'
: '/sphere/chat/$_roomId/messages/${editingTo.id}', : '/sphere/chat/$roomId/messages/${editingTo.id}',
data: { data: {
'content': content, 'content': content,
'attachments_id': cloudAttachments.map((e) => e.id).toList(), 'attachments_id': cloudAttachments.map((e) => e.id).toList(),
@@ -731,7 +726,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> receiveMessage(SnChatMessage remoteMessage) async { Future<void> receiveMessage(SnChatMessage remoteMessage) async {
if (remoteMessage.chatRoomId != _roomId) return; if (remoteMessage.chatRoomId != roomId) return;
// Block message receiving during jumps to prevent list resets // Block message receiving during jumps to prevent list resets
if (_isJumping) { if (_isJumping) {
@@ -783,7 +778,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async { Future<void> receiveMessageUpdate(SnChatMessage remoteMessage) async {
if (remoteMessage.chatRoomId != _roomId) return; if (remoteMessage.chatRoomId != roomId) return;
// Block message updates during jumps to prevent list resets // Block message updates during jumps to prevent list resets
if (_isJumping) { if (_isJumping) {
@@ -883,7 +878,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
try { try {
await _apiClient.delete('/sphere/chat/$_roomId/messages/$messageId'); await _apiClient.delete('/sphere/chat/$roomId/messages/$messageId');
await receiveMessageDeletion(messageId); await receiveMessageDeletion(messageId);
} catch (err, stackTrace) { } catch (err, stackTrace) {
talker.log( talker.log(
@@ -991,7 +986,7 @@ class MessagesNotifier extends _$MessagesNotifier {
} }
final response = await _apiClient.get( final response = await _apiClient.get(
'/sphere/chat/$_roomId/messages/$messageId', '/sphere/chat/$roomId/messages/$messageId',
); );
final remoteMessage = SnChatMessage.fromJson(response.data); final remoteMessage = SnChatMessage.fromJson(response.data);
final message = LocalChatMessage.fromRemoteMessage( final message = LocalChatMessage.fromRemoteMessage(
@@ -1048,7 +1043,7 @@ class MessagesNotifier extends _$MessagesNotifier {
final query = _database.customSelect( final query = _database.customSelect(
'SELECT COUNT(*) as count FROM chat_messages WHERE room_id = ? AND created_at > ?', 'SELECT COUNT(*) as count FROM chat_messages WHERE room_id = ? AND created_at > ?',
variables: [ variables: [
Variable.withString(_roomId), Variable.withString(roomId),
Variable.withDateTime(message.createdAt), Variable.withDateTime(message.createdAt),
], ],
readsFrom: {_database.chatMessages}, readsFrom: {_database.chatMessages},

View File

@@ -6,7 +6,7 @@ part of 'messages_notifier.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$messagesNotifierHash() => r'27ce32c54e317a04e1d554ed4a70a24e4503fdd1'; String _$messagesNotifierHash() => r'd76d799494b06fac2adc42d94b7ecd7b8d68c352';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {

View File

@@ -2,14 +2,24 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/file_list_item.dart'; import 'package:island/models/file_list_item.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'file_list.g.dart'; part 'file_list.g.dart';
@riverpod @riverpod
class CloudFileListNotifier extends _$CloudFileListNotifier Future<Map<String, dynamic>?> billingUsage(Ref ref) async {
with CursorPagingNotifierMixin<FileListItem> { final client = ref.read(apiClientProvider);
final response = await client.get('/drive/billing/usage');
return response.data;
}
final indexedCloudFileListNotifierProvider = AsyncNotifierProvider(
IndexedCloudFileListNotifier.new,
);
class IndexedCloudFileListNotifier extends AsyncNotifier<List<FileListItem>>
with AsyncPaginationController<FileListItem> {
String _currentPath = '/'; String _currentPath = '/';
String? _poolId; String? _poolId;
String? _query; String? _query;
@@ -42,12 +52,7 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
} }
@override @override
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null); Future<List<FileListItem>> fetch() async {
@override
Future<CursorPagingData<FileListItem>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final queryParameters = <String, String>{'path': _currentPath}; final queryParameters = <String, String>{'path': _currentPath};
@@ -83,21 +88,16 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
...files.map((file) => FileListItem.file(file)), ...files.map((file) => FileListItem.file(file)),
]; ];
// The new API returns all files in the path, no pagination return items;
return CursorPagingData(items: items, hasMore: false, nextCursor: null);
} }
} }
@riverpod final unindexedFileListNotifierProvider = AsyncNotifierProvider(
Future<Map<String, dynamic>?> billingUsage(Ref ref) async { UnindexedFileListNotifier.new,
final client = ref.read(apiClientProvider); );
final response = await client.get('/drive/billing/usage');
return response.data;
}
@riverpod class UnindexedFileListNotifier extends AsyncNotifier<List<FileListItem>>
class UnindexedFileListNotifier extends _$UnindexedFileListNotifier with AsyncPaginationController<FileListItem> {
with CursorPagingNotifierMixin<FileListItem> {
String? _poolId; String? _poolId;
bool _recycled = false; bool _recycled = false;
String? _query; String? _query;
@@ -129,21 +129,15 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
ref.invalidateSelf(); ref.invalidateSelf();
} }
@override static const int pageSize = 20;
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
@override @override
Future<CursorPagingData<FileListItem>> fetch({ Future<List<FileListItem>> fetch() async {
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor != null ? int.tryParse(cursor) ?? 0 : 0;
const take = 50; // Default page size
final queryParameters = <String, String>{ final queryParameters = <String, String>{
'take': take.toString(), 'take': pageSize.toString(),
'offset': offset.toString(), 'offset': fetchedCount.toString(),
}; };
if (_poolId != null) { if (_poolId != null) {
@@ -169,7 +163,7 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
queryParameters: queryParameters, queryParameters: queryParameters,
); );
final total = int.tryParse(response.headers.value('x-total') ?? '0') ?? 0; totalCount = int.tryParse(response.headers.value('x-total') ?? '0') ?? 0;
final List<SnCloudFile> files = final List<SnCloudFile> files =
(response.data as List) (response.data as List)
@@ -179,14 +173,7 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
final List<FileListItem> items = final List<FileListItem> items =
files.map((file) => FileListItem.unindexedFile(file)).toList(); files.map((file) => FileListItem.unindexedFile(file)).toList();
final hasMore = offset + take < total; return items;
final nextCursor = hasMore ? (offset + take).toString() : null;
return CursorPagingData(
items: items,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }

View File

@@ -44,47 +44,5 @@ final billingQuotaProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef BillingQuotaRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>; typedef BillingQuotaRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>;
String _$cloudFileListNotifierHash() =>
r'533dfa86f920b60cf7491fb4aeb95ece19e428af';
/// See also [CloudFileListNotifier].
@ProviderFor(CloudFileListNotifier)
final cloudFileListNotifierProvider = AutoDisposeAsyncNotifierProvider<
CloudFileListNotifier,
CursorPagingData<FileListItem>
>.internal(
CloudFileListNotifier.new,
name: r'cloudFileListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$cloudFileListNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$CloudFileListNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<FileListItem>>;
String _$unindexedFileListNotifierHash() =>
r'afa487d7b956b71b21ca1b073a01364a34ede1d5';
/// See also [UnindexedFileListNotifier].
@ProviderFor(UnindexedFileListNotifier)
final unindexedFileListNotifierProvider = AutoDisposeAsyncNotifierProvider<
UnindexedFileListNotifier,
CursorPagingData<FileListItem>
>.internal(
UnindexedFileListNotifier.new,
name: r'unindexedFileListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$unindexedFileListNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$UnindexedFileListNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<FileListItem>>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

84
lib/pods/paging.dart Normal file
View File

@@ -0,0 +1,84 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
abstract class PaginationController<T> {
int? get totalCount;
int get fetchedCount;
bool get fetchedAll;
FutureOr<List<T>> fetch();
Future<void> refresh();
Future<void> fetchFurther();
}
abstract class PaginationFiltered<F> {
late F currentFilter;
Future<void> applyFilter(F filter);
}
mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
implements PaginationController<T> {
@override
int? totalCount;
@override
int fetchedCount = 0;
@override
bool get fetchedAll => totalCount != null && fetchedCount >= totalCount!;
@override
FutureOr<List<T>> build() async => fetch();
@override
Future<void> refresh() async {
totalCount = null;
fetchedCount = 0;
state = AsyncData<List<T>>([]);
final newState = await AsyncValue.guard<List<T>>(() async {
return await fetch();
});
state = newState;
fetchedCount = newState.value?.length ?? 0;
}
@override
Future<void> fetchFurther() async {
if (fetchedAll) return;
state = AsyncLoading<List<T>>();
final newState = await AsyncValue.guard<List<T>>(() async {
final elements = await fetch();
return [...?state.valueOrNull, ...elements];
});
state = newState;
fetchedCount = newState.value?.length ?? 0;
}
}
mixin AsyncPaginationFilter<F, T> on AsyncPaginationController<T>
implements PaginationFiltered<F> {
@override
Future<void> applyFilter(F filter) async {
if (currentFilter == filter) return;
// Reset the data
totalCount = null;
fetchedCount = 0;
currentFilter = filter;
state = AsyncData<List<T>>([]);
final newState = await AsyncValue.guard<List<T>>(() async {
return await fetch();
});
state = newState;
}
}

65
lib/pods/timeline.dart Normal file
View File

@@ -0,0 +1,65 @@
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/activity.dart';
import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
final activityListNotifierProvider =
AsyncNotifierProvider<ActivityListNotifier, List<SnTimelineEvent>>(
ActivityListNotifier.new,
);
class ActivityListNotifier extends AsyncNotifier<List<SnTimelineEvent>>
with
AsyncPaginationController<SnTimelineEvent>,
AsyncPaginationFilter<String?, SnTimelineEvent> {
static const int pageSize = 20;
@override
String? currentFilter;
@override
Future<List<SnTimelineEvent>> fetch() async {
final client = ref.read(apiClientProvider);
final cursor =
state.valueOrNull?.lastOrNull?.createdAt.toUtc().toIso8601String();
final queryParameters = {
if (cursor != null) 'cursor': cursor,
'take': pageSize,
if (currentFilter != null) 'filter': currentFilter,
if (kDebugMode)
'debugInclude': 'realms,publishers,articles,shuffledPosts',
};
final response = await client.get(
'/sphere/timeline',
queryParameters: queryParameters,
);
final List<SnTimelineEvent> items =
(response.data as List)
.map((e) => SnTimelineEvent.fromJson(e as Map<String, dynamic>))
.toList();
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
totalCount =
(state.valueOrNull?.length ?? 0) +
items.length +
(hasMore ? pageSize : 0);
return items;
}
void updateOne(int index, SnTimelineEvent activity) {
final currentState = state.valueOrNull;
if (currentState == null) return;
final updatedItems = [...currentState];
updatedItems[index] = activity;
state = AsyncData(updatedItems);
}
}

View File

@@ -4,9 +4,11 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/services/time.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
part 'credits.g.dart'; part 'credits.g.dart';
@@ -21,40 +23,35 @@ Future<double> socialCredits(Ref ref) async {
return response.data?.toDouble() ?? 0.0; return response.data?.toDouble() ?? 0.0;
} }
@riverpod final socialCreditHistoryNotifierProvider = AsyncNotifierProvider(
class SocialCreditHistoryNotifier extends _$SocialCreditHistoryNotifier SocialCreditHistoryNotifier.new,
with CursorPagingNotifierMixin<SnSocialCreditRecord> { );
static const int _pageSize = 20;
class SocialCreditHistoryNotifier
extends AsyncNotifier<List<SnSocialCreditRecord>>
with AsyncPaginationController<SnSocialCreditRecord> {
static const int pageSize = 20;
@override @override
Future<CursorPagingData<SnSocialCreditRecord>> build() => fetch(cursor: null); Future<List<SnSocialCreditRecord>> fetch() async {
@override
Future<CursorPagingData<SnSocialCreditRecord>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = {'offset': offset, 'take': _pageSize}; final queryParams = {'offset': fetchedCount.toString(), 'take': pageSize};
final response = await client.get( final response = await client.get(
'/pass/accounts/me/credits/history', '/pass/accounts/me/credits/history',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final records = final records =
data.map((json) => SnSocialCreditRecord.fromJson(json)).toList(); response.data
.map((json) => SnSocialCreditRecord.fromJson(json))
.cast<SnSocialCreditRecord>()
.toList();
final hasMore = offset + records.length < total; return records;
final nextCursor = hasMore ? (offset + records.length).toString() : null;
return CursorPagingData(
items: records,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -110,31 +107,39 @@ class SocialCreditsTab extends HookConsumerWidget {
.padding(horizontal: 20, vertical: 16), .padding(horizontal: 20, vertical: 16),
), ),
Expanded( Expanded(
child: PagingHelperView( child: PaginationList(
provider: socialCreditHistoryNotifierProvider,
futureRefreshable: socialCreditHistoryNotifierProvider.future,
notifierRefreshable: socialCreditHistoryNotifierProvider.notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: widgetCount, provider: socialCreditHistoryNotifierProvider,
itemBuilder: (context, index) { notifier: socialCreditHistoryNotifierProvider.notifier,
if (index == widgetCount - 1) { itemBuilder: (context, idx, record) {
return endItemView; final isExpired =
} record.expiredAt != null &&
final record = data.items[index]; record.expiredAt!.isBefore(DateTime.now());
return ListTile( return ListTile(
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(horizontal: 24),
horizontal: 24, title: Text(
record.reason,
style:
isExpired
? TextStyle(
decoration: TextDecoration.lineThrough,
color: Theme.of(
context,
).colorScheme.onSurface.withOpacity(0.8),
)
: null,
), ),
title: Text(record.reason), subtitle: Row(
subtitle: Text( spacing: 4,
DateFormat.yMMMd().format(record.createdAt), children: [
Text(record.createdAt.formatSystem()),
Text('to'),
if (record.expiredAt != null)
Text(record.expiredAt!.formatSystem()),
],
), ),
trailing: Text( trailing: Text(
record.delta > 0 record.delta > 0 ? '+${record.delta}' : '${record.delta}',
? '+${record.delta}'
: '${record.delta}',
style: TextStyle( style: TextStyle(
color: record.delta > 0 ? Colors.green : Colors.red, color: record.delta > 0 ? Colors.green : Colors.red,
), ),
@@ -143,7 +148,6 @@ class SocialCreditsTab extends HookConsumerWidget {
}, },
), ),
), ),
),
], ],
); );
} }

View File

@@ -24,26 +24,5 @@ final socialCreditsProvider = AutoDisposeFutureProvider<double>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef SocialCreditsRef = AutoDisposeFutureProviderRef<double>; typedef SocialCreditsRef = AutoDisposeFutureProviderRef<double>;
String _$socialCreditHistoryNotifierHash() =>
r'3e87af246cc5dc72a1f3a87b81d1c87169bdfb5b';
/// See also [SocialCreditHistoryNotifier].
@ProviderFor(SocialCreditHistoryNotifier)
final socialCreditHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider<
SocialCreditHistoryNotifier,
CursorPagingData<SnSocialCreditRecord>
>.internal(
SocialCreditHistoryNotifier.new,
name: r'socialCreditHistoryNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$socialCreditHistoryNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SocialCreditHistoryNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<SnSocialCreditRecord>>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -3,6 +3,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/screens/account/credits.dart'; import 'package:island/screens/account/credits.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
@@ -10,46 +11,37 @@ import 'package:island/widgets/account/leveling_progress.dart';
import 'package:island/widgets/account/stellar_program_tab.dart'; import 'package:island/widgets/account/stellar_program_tab.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:island/widgets/paging/pagination_list.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
part 'leveling.g.dart'; final levelingHistoryNotifierProvider = AsyncNotifierProvider(
LevelingHistoryNotifier.new,
);
@riverpod class LevelingHistoryNotifier extends AsyncNotifier<List<SnExperienceRecord>>
class LevelingHistoryNotifier extends _$LevelingHistoryNotifier with AsyncPaginationController<SnExperienceRecord> {
with CursorPagingNotifierMixin<SnExperienceRecord> { static const int pageSize = 20;
static const int _pageSize = 20;
@override @override
Future<CursorPagingData<SnExperienceRecord>> build() => fetch(cursor: null); Future<List<SnExperienceRecord>> fetch() async {
@override
Future<CursorPagingData<SnExperienceRecord>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final queryParams = {'offset': offset, 'take': _pageSize}; final queryParams = {'offset': fetchedCount.toString(), 'take': pageSize};
final response = await client.get( final response = await client.get(
'/pass/accounts/me/leveling', '/pass/accounts/me/leveling',
queryParameters: queryParams, queryParameters: queryParams,
); );
final total = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data;
final records =
data.map((json) => SnExperienceRecord.fromJson(json)).toList();
final hasMore = offset + records.length < total; totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final nextCursor = hasMore ? (offset + records.length).toString() : null;
return CursorPagingData( final List<SnExperienceRecord> records =
items: records, response.data
hasMore: hasMore, .map((json) => SnExperienceRecord.fromJson(json))
nextCursor: nextCursor, .cast<SnExperienceRecord>()
); .toList();
return records;
} }
} }
@@ -189,19 +181,13 @@ class LevelingScreen extends HookConsumerWidget {
), ),
), ),
const SliverGap(8), const SliverGap(8),
PagingHelperSliverView( PaginationList(
provider: levelingHistoryNotifierProvider, provider: levelingHistoryNotifierProvider,
futureRefreshable: levelingHistoryNotifierProvider.future, notifier: levelingHistoryNotifierProvider.notifier,
notifierRefreshable: levelingHistoryNotifierProvider.notifier, isRefreshable: false,
contentBuilder: isSliver: true,
(data, widgetCount, endItemView) => SliverList.builder( itemBuilder:
itemCount: widgetCount, (context, idx, record) => ListTile(
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final record = data.items[index];
return ListTile(
title: Column( title: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -214,9 +200,7 @@ class LevelingScreen extends HookConsumerWidget {
record.createdAt.formatRelative(context), record.createdAt.formatRelative(context),
).fontSize(13), ).fontSize(13),
Text('·').fontSize(13).bold(), Text('·').fontSize(13).bold(),
Text( Text(record.createdAt.formatSystem()).fontSize(13),
record.createdAt.formatSystem(),
).fontSize(13),
], ],
).opacity(0.8), ).opacity(0.8),
], ],
@@ -233,8 +217,6 @@ class LevelingScreen extends HookConsumerWidget {
), ),
minTileHeight: 56, minTileHeight: 56,
contentPadding: EdgeInsets.symmetric(horizontal: 4), contentPadding: EdgeInsets.symmetric(horizontal: 4),
);
},
), ),
), ),

View File

@@ -1,31 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'leveling.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$levelingHistoryNotifierHash() =>
r'de51012e1590ac46388b6f3f2050b21cb96698d1';
/// See also [LevelingHistoryNotifier].
@ProviderFor(LevelingHistoryNotifier)
final levelingHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider<
LevelingHistoryNotifier,
CursorPagingData<SnExperienceRecord>
>.internal(
LevelingHistoryNotifier.new,
name: r'levelingHistoryNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$levelingHistoryNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$LevelingHistoryNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<SnExperienceRecord>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart';
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:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -7,9 +6,6 @@ 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';
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/models/file.dart';
import 'package:island/models/account.dart';
import 'package:island/pods/database.dart';
import 'package:island/pods/chat/chat_summary.dart'; import 'package:island/pods/chat/chat_summary.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
@@ -24,11 +20,9 @@ import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:island/widgets/response.dart'; import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:relative_time/relative_time.dart'; import 'package:relative_time/relative_time.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:super_sliver_list/super_sliver_list.dart'; import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:island/pods/chat/chat_room.dart';
part 'chat.g.dart';
class ChatRoomListTile extends HookConsumerWidget { class ChatRoomListTile extends HookConsumerWidget {
final SnChatRoom room; final SnChatRoom room;
@@ -183,90 +177,6 @@ class ChatRoomListTile extends HookConsumerWidget {
} }
} }
@riverpod
Future<List<SnChatRoom>> chatroomsJoined(Ref ref) async {
final db = ref.watch(databaseProvider);
try {
final localRoomsData = await db.select(db.chatRooms).get();
if (localRoomsData.isNotEmpty) {
final localRooms = await Future.wait(
localRoomsData.map((row) async {
final membersRows =
await (db.select(db.chatMembers)
..where((m) => m.chatRoomId.equals(row.id))).get();
final members =
membersRows.map((mRow) {
final account = SnAccount.fromJson(mRow.account);
return SnChatMember(
id: mRow.id,
chatRoomId: mRow.chatRoomId,
accountId: mRow.accountId,
account: account,
nick: mRow.nick,
notify: mRow.notify,
joinedAt: mRow.joinedAt,
breakUntil: mRow.breakUntil,
timeoutUntil: mRow.timeoutUntil,
status: null,
createdAt: mRow.createdAt,
updatedAt: mRow.updatedAt,
deletedAt: mRow.deletedAt,
chatRoom: null,
);
}).toList();
return SnChatRoom(
id: row.id,
name: row.name,
description: row.description,
type: row.type,
isPublic: row.isPublic!,
isCommunity: row.isCommunity!,
picture:
row.picture != null ? SnCloudFile.fromJson(row.picture!) : null,
background:
row.background != null
? SnCloudFile.fromJson(row.background!)
: null,
realmId: row.realmId,
accountId: row.accountId,
realm: null,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
members: members,
);
}),
);
// Background sync
Future(() async {
try {
final client = ref.read(apiClientProvider);
final resp = await client.get('/sphere/chat');
final remoteRooms =
resp.data
.map((e) => SnChatRoom.fromJson(e))
.cast<SnChatRoom>()
.toList();
await db.saveChatRooms(remoteRooms);
ref.invalidateSelf();
} catch (_) {}
}).ignore();
return localRooms;
}
} catch (_) {}
// Fallback to API
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat');
final rooms =
resp.data.map((e) => SnChatRoom.fromJson(e)).cast<SnChatRoom>().toList();
await db.saveChatRooms(rooms);
return rooms;
}
class ChatListBodyWidget extends HookConsumerWidget { class ChatListBodyWidget extends HookConsumerWidget {
final bool isFloating; final bool isFloating;
final TabController tabController; final TabController tabController;
@@ -281,7 +191,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chats = ref.watch(chatroomsJoinedProvider); final chats = ref.watch(chatRoomJoinedNotifierProvider);
Widget bodyWidget = Column( Widget bodyWidget = Column(
children: [ children: [
@@ -304,7 +214,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
(items) => RefreshIndicator( (items) => RefreshIndicator(
onRefresh: onRefresh:
() => Future.sync(() { () => Future.sync(() {
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
}), }),
child: SuperListView.builder( child: SuperListView.builder(
padding: EdgeInsets.only(bottom: 96), padding: EdgeInsets.only(bottom: 96),
@@ -354,7 +264,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
(error, stack) => ResponseErrorWidget( (error, stack) => ResponseErrorWidget(
error: error, error: error,
onRetry: () { onRetry: () {
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
}, },
), ),
), ),
@@ -431,7 +341,7 @@ class ChatListScreen extends HookConsumerWidget {
// Listen for chat rooms refresh events // Listen for chat rooms refresh events
final subscription = eventBus.on<ChatRoomsRefreshEvent>().listen((event) { final subscription = eventBus.on<ChatRoomsRefreshEvent>().listen((event) {
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
}); });
return () { return () {
@@ -599,46 +509,6 @@ class ChatListScreen extends HookConsumerWidget {
} }
} }
@riverpod
Future<SnChatRoom?> chatroom(Ref ref, String? identifier) async {
if (identifier == null) return null;
try {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/$identifier');
return SnChatRoom.fromJson(resp.data);
} catch (err) {
if (err is DioException && err.response?.statusCode == 404) {
return null; // Chat room not found
}
rethrow; // Rethrow other errors
}
}
@riverpod
Future<SnChatMember?> chatroomIdentity(Ref ref, String? identifier) async {
if (identifier == null) return null;
try {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/$identifier/members/me');
return SnChatMember.fromJson(resp.data);
} catch (err) {
if (err is DioException && err.response?.statusCode == 404) {
return null; // Chat member not found
}
rethrow; // Rethrow other errors
}
}
@riverpod
Future<List<SnChatMember>> chatroomInvites(Ref ref) async {
final client = ref.watch(apiClientProvider);
final resp = await client.get('/sphere/chat/invites');
return resp.data
.map((e) => SnChatMember.fromJson(e))
.cast<SnChatMember>()
.toList();
}
class _ChatInvitesSheet extends HookConsumerWidget { class _ChatInvitesSheet extends HookConsumerWidget {
const _ChatInvitesSheet(); const _ChatInvitesSheet();
@@ -651,7 +521,7 @@ class _ChatInvitesSheet extends HookConsumerWidget {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept'); await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept');
ref.invalidate(chatroomInvitesProvider); ref.invalidate(chatroomInvitesProvider);
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} }

View File

@@ -1,309 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$chatroomsJoinedHash() => r'50abce4f03a7a8509f16d5ad0b1dbf8e3aeb73b6';
/// See also [chatroomsJoined].
@ProviderFor(chatroomsJoined)
final chatroomsJoinedProvider =
AutoDisposeFutureProvider<List<SnChatRoom>>.internal(
chatroomsJoined,
name: r'chatroomsJoinedProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomsJoinedHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChatRoom>>;
String _$chatroomHash() => r'2b17d94728026420d18d6c383d2400cf4a070913';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [chatroom].
@ProviderFor(chatroom)
const chatroomProvider = ChatroomFamily();
/// See also [chatroom].
class ChatroomFamily extends Family<AsyncValue<SnChatRoom?>> {
/// See also [chatroom].
const ChatroomFamily();
/// See also [chatroom].
ChatroomProvider call(String? identifier) {
return ChatroomProvider(identifier);
}
@override
ChatroomProvider getProviderOverride(covariant ChatroomProvider provider) {
return call(provider.identifier);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'chatroomProvider';
}
/// See also [chatroom].
class ChatroomProvider extends AutoDisposeFutureProvider<SnChatRoom?> {
/// See also [chatroom].
ChatroomProvider(String? identifier)
: this._internal(
(ref) => chatroom(ref as ChatroomRef, identifier),
from: chatroomProvider,
name: r'chatroomProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomHash,
dependencies: ChatroomFamily._dependencies,
allTransitiveDependencies: ChatroomFamily._allTransitiveDependencies,
identifier: identifier,
);
ChatroomProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.identifier,
}) : super.internal();
final String? identifier;
@override
Override overrideWith(
FutureOr<SnChatRoom?> Function(ChatroomRef provider) create,
) {
return ProviderOverride(
origin: this,
override: ChatroomProvider._internal(
(ref) => create(ref as ChatroomRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
identifier: identifier,
),
);
}
@override
AutoDisposeFutureProviderElement<SnChatRoom?> createElement() {
return _ChatroomProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ChatroomProvider && other.identifier == identifier;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, identifier.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ChatroomRef on AutoDisposeFutureProviderRef<SnChatRoom?> {
/// The parameter `identifier` of this provider.
String? get identifier;
}
class _ChatroomProviderElement
extends AutoDisposeFutureProviderElement<SnChatRoom?>
with ChatroomRef {
_ChatroomProviderElement(super.provider);
@override
String? get identifier => (origin as ChatroomProvider).identifier;
}
String _$chatroomIdentityHash() => r'35e19a5a3e31752c79b97ba0358a7ec8fb8f6e99';
/// See also [chatroomIdentity].
@ProviderFor(chatroomIdentity)
const chatroomIdentityProvider = ChatroomIdentityFamily();
/// See also [chatroomIdentity].
class ChatroomIdentityFamily extends Family<AsyncValue<SnChatMember?>> {
/// See also [chatroomIdentity].
const ChatroomIdentityFamily();
/// See also [chatroomIdentity].
ChatroomIdentityProvider call(String? identifier) {
return ChatroomIdentityProvider(identifier);
}
@override
ChatroomIdentityProvider getProviderOverride(
covariant ChatroomIdentityProvider provider,
) {
return call(provider.identifier);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'chatroomIdentityProvider';
}
/// See also [chatroomIdentity].
class ChatroomIdentityProvider
extends AutoDisposeFutureProvider<SnChatMember?> {
/// See also [chatroomIdentity].
ChatroomIdentityProvider(String? identifier)
: this._internal(
(ref) => chatroomIdentity(ref as ChatroomIdentityRef, identifier),
from: chatroomIdentityProvider,
name: r'chatroomIdentityProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomIdentityHash,
dependencies: ChatroomIdentityFamily._dependencies,
allTransitiveDependencies:
ChatroomIdentityFamily._allTransitiveDependencies,
identifier: identifier,
);
ChatroomIdentityProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.identifier,
}) : super.internal();
final String? identifier;
@override
Override overrideWith(
FutureOr<SnChatMember?> Function(ChatroomIdentityRef provider) create,
) {
return ProviderOverride(
origin: this,
override: ChatroomIdentityProvider._internal(
(ref) => create(ref as ChatroomIdentityRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
identifier: identifier,
),
);
}
@override
AutoDisposeFutureProviderElement<SnChatMember?> createElement() {
return _ChatroomIdentityProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ChatroomIdentityProvider && other.identifier == identifier;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, identifier.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ChatroomIdentityRef on AutoDisposeFutureProviderRef<SnChatMember?> {
/// The parameter `identifier` of this provider.
String? get identifier;
}
class _ChatroomIdentityProviderElement
extends AutoDisposeFutureProviderElement<SnChatMember?>
with ChatroomIdentityRef {
_ChatroomIdentityProviderElement(super.provider);
@override
String? get identifier => (origin as ChatroomIdentityProvider).identifier;
}
String _$chatroomInvitesHash() => r'5cd6391b09c5517ede19bacce43b45c8d71dd087';
/// See also [chatroomInvites].
@ProviderFor(chatroomInvites)
final chatroomInvitesProvider =
AutoDisposeFutureProvider<List<SnChatMember>>.internal(
chatroomInvites,
name: r'chatroomInvitesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$chatroomInvitesHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ChatroomInvitesRef = AutoDisposeFutureProviderRef<List<SnChatMember>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -10,8 +10,8 @@ import 'package:image_picker/image_picker.dart';
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/realm.dart'; import 'package:island/models/realm.dart';
import 'package:island/pods/chat/chat_room.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/screens/chat/chat.dart';
import 'package:island/screens/realm/realms.dart'; import 'package:island/screens/realm/realms.dart';
import 'package:island/services/file.dart'; import 'package:island/services/file.dart';
import 'package:island/services/file_uploader.dart'; import 'package:island/services/file_uploader.dart';
@@ -47,7 +47,7 @@ class EditChatScreen extends HookConsumerWidget {
final isPublic = useState(true); final isPublic = useState(true);
final isCommunity = useState(false); final isCommunity = useState(false);
final chat = ref.watch(chatroomProvider(id)); final chat = ref.watch(ChatRoomNotifierProvider(id));
final joinedRealms = ref.watch(realmsJoinedProvider); final joinedRealms = ref.watch(realmsJoinedProvider);
final currentRealm = useState<SnRealm?>(null); final currentRealm = useState<SnRealm?>(null);

View File

@@ -3,7 +3,7 @@ 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";
import "package:island/database/message.dart"; import "package:island/database/message.dart";
import "package:island/screens/chat/chat.dart"; import "package:island/pods/chat/chat_room.dart";
import "package:island/widgets/content/cloud_files.dart"; import "package:island/widgets/content/cloud_files.dart";
import "package:super_sliver_list/super_sliver_list.dart"; import "package:super_sliver_list/super_sliver_list.dart";
import "package:easy_localization/easy_localization.dart"; import "package:easy_localization/easy_localization.dart";
@@ -203,7 +203,7 @@ class PublicRoomPreview extends HookConsumerWidget {
showLoadingModal(context); showLoadingModal(context);
final apiClient = ref.read(apiClientProvider); final apiClient = ref.read(apiClientProvider);
await apiClient.post('/sphere/chat/${room.id}/members/me'); await apiClient.post('/sphere/chat/${room.id}/members/me');
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(ChatRoomIdentityNotifierProvider(id));
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {

View File

@@ -14,15 +14,15 @@ import "package:island/models/chat.dart";
import "package:island/models/file.dart"; import "package:island/models/file.dart";
import "package:island/models/poll.dart"; import "package:island/models/poll.dart";
import "package:island/models/wallet.dart"; import "package:island/models/wallet.dart";
import "package:island/pods/chat/chat_rooms.dart"; import "package:island/pods/chat/chat_room.dart";
import "package:island/pods/chat/chat_subscribe.dart"; import "package:island/pods/chat/chat_subscribe.dart";
import "package:island/pods/chat/messages_notifier.dart"; import "package:island/pods/chat/messages_notifier.dart";
import "package:island/pods/network.dart"; import "package:island/pods/network.dart";
import "package:island/pods/chat/chat_online_count.dart"; import "package:island/pods/chat/chat_online_count.dart";
import "package:island/pods/config.dart"; import "package:island/pods/config.dart";
import "package:island/pods/userinfo.dart";
import "package:island/screens/chat/search_messages.dart"; import "package:island/screens/chat/search_messages.dart";
import "package:island/services/file_uploader.dart"; import "package:island/services/file_uploader.dart";
import "package:island/screens/chat/chat.dart";
import "package:island/services/responsive.dart"; import "package:island/services/responsive.dart";
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";
@@ -48,14 +48,12 @@ class ChatRoomScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chatRoom = ref.watch(chatroomProvider(id)); final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
final chatIdentity = ref.watch(chatroomIdentityProvider(id)); final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final isSyncing = ref.watch(isSyncingProvider); final isSyncing = ref.watch(isSyncingProvider);
final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id)); final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id));
final settings = ref.watch(appSettingsNotifierProvider); final settings = ref.watch(appSettingsNotifierProvider);
final hasOnlineCount = onlineCount.hasValue;
if (chatIdentity.isLoading || chatRoom.isLoading) { if (chatIdentity.isLoading || chatRoom.isLoading) {
return AppScaffold( return AppScaffold(
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
@@ -102,7 +100,9 @@ class ChatRoomScreen extends HookConsumerWidget {
await apiClient.post( await apiClient.post(
'/sphere/chat/${room.id}/members/me', '/sphere/chat/${room.id}/members/me',
); );
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(
ChatRoomIdentityNotifierProvider(id),
);
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {
@@ -131,7 +131,7 @@ class ChatRoomScreen extends HookConsumerWidget {
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: ResponseErrorWidget( body: ResponseErrorWidget(
error: error, error: error,
onRetry: () => ref.refresh(chatroomProvider(id)), onRetry: () => ref.refresh(ChatRoomNotifierProvider(id)),
), ),
), ),
); );
@@ -410,19 +410,27 @@ class ChatRoomScreen extends HookConsumerWidget {
final compactHeader = isWideScreen(context); final compactHeader = isWideScreen(context);
final userInfo = ref.watch(userInfoProvider);
List<SnChatMember> getValidMembers(List<SnChatMember> members) {
return members
.where((member) => member.accountId != userInfo.value?.id)
.toList();
}
Widget comfortHeaderWidget(SnChatRoom? room) => Column( Widget comfortHeaderWidget(SnChatRoom? room) => Column(
spacing: 4, spacing: 4,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Badge( Badge(
isLabelVisible: hasOnlineCount, isLabelVisible: (onlineCount.value ?? 0) > 1,
label: Text('${(onlineCount as AsyncData?)?.value}'), label: Text('${(onlineCount.value ?? 0)}'),
textStyle: GoogleFonts.robotoMono(fontSize: 10),
textColor: Colors.white,
backgroundColor: backgroundColor:
(onlineCount as AsyncData?)?.value != null && (onlineCount.value ?? 0) > 1 ? Colors.green : Colors.grey,
(onlineCount as AsyncData).value > 1 offset: Offset(6, 14),
? Colors.green
: Colors.grey,
child: SizedBox( child: SizedBox(
height: 26, height: 26,
width: 26, width: 26,
@@ -430,9 +438,9 @@ class ChatRoomScreen extends HookConsumerWidget {
(room!.type == 1 && room.picture?.id == null) (room!.type == 1 && room.picture?.id == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: filesId:
room.members! getValidMembers(
.map((e) => e.account.profile.picture?.id) room.members!,
.toList(), ).map((e) => e.account.profile.picture?.id).toList(),
) )
: room.picture?.id != null : room.picture?.id != null
? ProfilePictureWidget( ? ProfilePictureWidget(
@@ -449,7 +457,9 @@ class ChatRoomScreen extends HookConsumerWidget {
), ),
Text( Text(
(room.type == 1 && room.name == null) (room.type == 1 && room.name == null)
? room.members!.map((e) => e.account.nick).join(', ') ? getValidMembers(
room.members!,
).map((e) => e.account.nick).join(', ')
: room.name!, : room.name!,
).fontSize(15), ).fontSize(15),
], ],
@@ -462,7 +472,7 @@ class ChatRoomScreen extends HookConsumerWidget {
children: [ children: [
Badge( Badge(
isLabelVisible: (onlineCount.value ?? 0) > 1, isLabelVisible: (onlineCount.value ?? 0) > 1,
label: Text('${(onlineCount as AsyncData?)?.value}'), label: Text('${(onlineCount.value ?? 0)}'),
textStyle: GoogleFonts.robotoMono(fontSize: 10), textStyle: GoogleFonts.robotoMono(fontSize: 10),
backgroundColor: backgroundColor:
(onlineCount.value ?? 0) > 1 ? Colors.green : Colors.grey, (onlineCount.value ?? 0) > 1 ? Colors.green : Colors.grey,
@@ -475,9 +485,9 @@ class ChatRoomScreen extends HookConsumerWidget {
(room!.type == 1 && room.picture?.id == null) (room!.type == 1 && room.picture?.id == null)
? SplitAvatarWidget( ? SplitAvatarWidget(
filesId: filesId:
room.members! getValidMembers(
.map((e) => e.account.profile.picture?.id) room.members!,
.toList(), ).map((e) => e.account.profile.picture?.id).toList(),
) )
: room.picture?.id != null : room.picture?.id != null
? ProfilePictureWidget( ? ProfilePictureWidget(
@@ -494,7 +504,9 @@ class ChatRoomScreen extends HookConsumerWidget {
), ),
Text( Text(
(room.type == 1 && room.name == null) (room.type == 1 && room.name == null)
? room.members!.map((e) => e.account.nick).join(', ') ? getValidMembers(
room.members!,
).map((e) => e.account.nick).join(', ')
: room.name!, : room.name!,
).fontSize(19), ).fontSize(19),
], ],
@@ -734,7 +746,7 @@ class ChatRoomScreen extends HookConsumerWidget {
appBar: AppBar( appBar: AppBar(
leading: !compactHeader ? const Center(child: PageBackButton()) : null, leading: !compactHeader ? const Center(child: PageBackButton()) : null,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
toolbarHeight: compactHeader ? null : 80, toolbarHeight: compactHeader ? null : 74,
title: chatRoom.when( title: chatRoom.when(
data: data:
(room) => (room) =>

View File

@@ -7,8 +7,8 @@ import 'package:freezed_annotation/freezed_annotation.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/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/pods/chat/chat_room.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/screens/chat/chat.dart';
import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/account/account_pfc.dart';
import 'package:island/widgets/account/account_picker.dart'; import 'package:island/widgets/account/account_picker.dart';
import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/account/status.dart';
@@ -39,8 +39,8 @@ class ChatDetailScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final roomState = ref.watch(chatroomProvider(id)); final roomState = ref.watch(ChatRoomNotifierProvider(id));
final roomIdentity = ref.watch(chatroomIdentityProvider(id)); final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final totalMessages = ref.watch(totalMessagesCountProvider(id)); final totalMessages = ref.watch(totalMessagesCountProvider(id));
const kNotifyLevelText = [ const kNotifyLevelText = [
@@ -56,7 +56,7 @@ class ChatDetailScreen extends HookConsumerWidget {
'/sphere/chat/$id/members/me/notify', '/sphere/chat/$id/members/me/notify',
data: {'notify_level': level}, data: {'notify_level': level},
); );
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(ChatRoomIdentityNotifierProvider(id));
if (context.mounted) { if (context.mounted) {
showSnackBar( showSnackBar(
'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]), 'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]),
@@ -74,7 +74,7 @@ class ChatDetailScreen extends HookConsumerWidget {
'/sphere/chat/$id/members/me/notify', '/sphere/chat/$id/members/me/notify',
data: {'break_until': until.toUtc().toIso8601String()}, data: {'break_until': until.toUtc().toIso8601String()},
); );
ref.invalidate(chatroomProvider(id)); ref.invalidate(ChatRoomNotifierProvider(id));
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} }
@@ -439,8 +439,8 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chatIdentity = ref.watch(chatroomIdentityProvider(id)); final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
final chatRoom = ref.watch(chatroomProvider(id)); final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
final isManagable = final isManagable =
chatIdentity.value?.accountId == chatRoom.value?.accountId || chatIdentity.value?.accountId == chatRoom.value?.accountId ||
@@ -461,7 +461,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
// Invalidate to refresh room data after edit // Invalidate to refresh room data after edit
ref.invalidate(chatroomProvider(id)); ref.invalidate(ChatRoomNotifierProvider(id));
} }
}); });
}, },
@@ -497,7 +497,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id'); await client.delete('/sphere/chat/$id');
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
if (context.mounted) { if (context.mounted) {
context.pop(); context.pop();
} }
@@ -530,7 +530,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
if (confirm) { if (confirm) {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id/members/me'); await client.delete('/sphere/chat/$id/members/me');
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatRoomJoinedNotifierProvider);
if (context.mounted) { if (context.mounted) {
context.pop(); context.pop();
} }
@@ -648,8 +648,8 @@ class _ChatMemberListSheet extends HookConsumerWidget {
final memberState = ref.watch(chatMemberStateProvider(roomId)); final memberState = ref.watch(chatMemberStateProvider(roomId));
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier); final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
final roomIdentity = ref.watch(chatroomIdentityProvider(roomId)); final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(roomId));
final chatRoom = ref.watch(chatroomProvider(roomId)); final chatRoom = ref.watch(ChatRoomNotifierProvider(roomId));
final isManagable = final isManagable =
chatRoom.value?.accountId == roomIdentity.value?.accountId || chatRoom.value?.accountId == roomIdentity.value?.accountId ||

View File

@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/chat/chat_room.dart';
import 'package:island/pods/chat/messages_notifier.dart'; import 'package:island/pods/chat/messages_notifier.dart';
import 'package:island/pods/chat/chat_rooms.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/chat/message_list_tile.dart'; import 'package:island/widgets/chat/message_list_tile.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';

View File

@@ -573,6 +573,7 @@ class CreatorHubScreen extends HookConsumerWidget {
child: publisherStats.when( child: publisherStats.when(
data: data:
(stats) => SingleChildScrollView( (stats) => SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 24),
child: child:
currentPublisher.value == null currentPublisher.value == null
? ConstrainedBox( ? ConstrainedBox(
@@ -602,7 +603,7 @@ class CreatorHubScreen extends HookConsumerWidget {
).padding(horizontal: 12), ).padding(horizontal: 12),
buildNavigationWidget(true), buildNavigationWidget(true),
], ],
).padding(vertical: 24) )
: Column( : Column(
spacing: 12, spacing: 12,
children: [ children: [

View File

@@ -320,7 +320,6 @@ class StickerForm extends HookConsumerWidget {
final formKey = useMemoized(() => GlobalKey<FormState>(), []); final formKey = useMemoized(() => GlobalKey<FormState>(), []);
final image = useState<String?>(id == null ? '' : sticker.value?.image.id); final image = useState<String?>(id == null ? '' : sticker.value?.image.id);
final imageController = useTextEditingController(text: image.value);
final slugController = useTextEditingController( final slugController = useTextEditingController(
text: id == null ? '' : sticker.value?.slug, text: id == null ? '' : sticker.value?.slug,
); );
@@ -328,7 +327,6 @@ class StickerForm extends HookConsumerWidget {
useEffect(() { useEffect(() {
if (sticker.value != null) { if (sticker.value != null) {
image.value = sticker.value!.image.id; image.value = sticker.value!.image.id;
imageController.text = sticker.value!.image.id;
slugController.text = sticker.value!.slug; slugController.text = sticker.value!.slug;
} }
return null; return null;
@@ -344,7 +342,7 @@ class StickerForm extends HookConsumerWidget {
id == null id == null
? '/sphere/stickers/$packId/content' ? '/sphere/stickers/$packId/content'
: '/sphere/stickers/$packId/content/$id', : '/sphere/stickers/$packId/content/$id',
data: {'slug': slugController.text, 'image_id': imageController.text}, data: {'slug': slugController.text, 'image_id': image.value},
options: Options(method: id == null ? 'POST' : 'PATCH'), options: Options(method: id == null ? 'POST' : 'PATCH'),
); );
if (context.mounted) { if (context.mounted) {
@@ -392,7 +390,6 @@ class StickerForm extends HookConsumerWidget {
).then((value) { ).then((value) {
if (value == null) return; if (value == null) return;
image.value = value[0].id; image.value = value[0].id;
imageController.text = image.value!;
}); });
}, },
icon: const Icon(Symbols.cloud_upload), icon: const Icon(Symbols.cloud_upload),

View File

@@ -4,11 +4,14 @@ import 'package:flutter/material.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';
import 'package:island/models/file.dart';
import 'package:island/models/sticker.dart'; import 'package:island/models/sticker.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
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:island/widgets/content/cloud_file_picker.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:island/screens/creators/stickers/pack_detail.dart'; import 'package:island/screens/creators/stickers/pack_detail.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
@@ -207,6 +210,9 @@ class StickerPackForm extends HookConsumerWidget {
final formKey = useMemoized(() => GlobalKey<FormState>(), []); final formKey = useMemoized(() => GlobalKey<FormState>(), []);
final initialPack = ref.watch(stickerPackProvider(packId)); final initialPack = ref.watch(stickerPackProvider(packId));
final icon = useState<String?>(
packId == null ? '' : initialPack.value?.icon?.id,
);
final nameController = useTextEditingController(); final nameController = useTextEditingController();
final descriptionController = useTextEditingController(); final descriptionController = useTextEditingController();
final prefixController = useTextEditingController(); final prefixController = useTextEditingController();
@@ -229,11 +235,12 @@ class StickerPackForm extends HookConsumerWidget {
submitting.value = true; submitting.value = true;
final apiClient = ref.watch(apiClientProvider); final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.request( final resp = await apiClient.request(
'/sphere/stickers', packId == null ? '/sphere/stickers' : '/sphere/stickers/$packId',
data: { data: {
'name': nameController.text, 'name': nameController.text,
'description': descriptionController.text, 'description': descriptionController.text,
'prefix': prefixController.text, 'prefix': prefixController.text,
'icon_id': icon.value,
}, },
queryParameters: {'pub': pubName}, queryParameters: {'pub': pubName},
options: Options(method: packId == null ? 'POST' : 'PATCH'), options: Options(method: packId == null ? 'POST' : 'PATCH'),
@@ -255,6 +262,44 @@ class StickerPackForm extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 16, spacing: 16,
children: [ children: [
Row(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SizedBox(
height: 80,
width: 80,
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child:
(icon.value?.isEmpty ?? true)
? const SizedBox.shrink()
: CloudImageWidget(fileId: icon.value!),
),
),
),
IconButton.filledTonal(
onPressed: () {
showModalBottomSheet(
context: context,
builder:
(context) => CloudFilePicker(
allowedTypes: {UniversalFileType.image},
),
).then((value) {
if (value == null) return;
icon.value = value[0].id;
});
},
icon: const Icon(Symbols.cloud_upload),
),
],
),
TextFormField( TextFormField(
controller: nameController, controller: nameController,
decoration: InputDecoration( decoration: InputDecoration(

View File

@@ -1,6 +1,5 @@
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@@ -12,6 +11,7 @@ import 'package:island/models/publisher.dart';
import 'package:island/models/realm.dart'; import 'package:island/models/realm.dart';
import 'package:island/models/webfeed.dart'; import 'package:island/models/webfeed.dart';
import 'package:island/pods/event_calendar.dart'; import 'package:island/pods/event_calendar.dart';
import 'package:island/pods/timeline.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/screens/auth/login_modal.dart'; import 'package:island/screens/auth/login_modal.dart';
import 'package:island/screens/notification.dart'; import 'package:island/screens/notification.dart';
@@ -21,24 +21,20 @@ import 'package:island/widgets/account/friends_overview.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/widgets/check_in.dart'; import 'package:island/widgets/check_in.dart';
import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:island/widgets/navigation/fab_menu.dart'; import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:island/widgets/post/post_featured.dart'; import 'package:island/widgets/post/post_featured.dart';
import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/post_item.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/realm/realm_card.dart'; import 'package:island/widgets/realm/realm_card.dart';
import 'package:island/widgets/publisher/publisher_card.dart'; import 'package:island/widgets/publisher/publisher_card.dart';
import 'package:island/widgets/web_article_card.dart'; import 'package:island/widgets/web_article_card.dart';
import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:island/services/event_bus.dart'; import 'package:island/services/event_bus.dart';
import 'package:island/widgets/share/share_sheet.dart'; import 'package:island/widgets/share/share_sheet.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:super_sliver_list/super_sliver_list.dart'; import 'package:super_sliver_list/super_sliver_list.dart';
part 'explore.g.dart';
Widget notificationIndicatorWidget( Widget notificationIndicatorWidget(
BuildContext context, { BuildContext context, {
required int count, required int count,
@@ -76,8 +72,8 @@ class ExploreScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 3);
final currentFilter = useState<String?>(null); final currentFilter = useState<String?>(null);
final notifier = ref.watch(activityListNotifierProvider.notifier);
useEffect(() { useEffect(() {
// Set FAB type to chat // Set FAB type to chat
@@ -95,32 +91,15 @@ class ExploreScreen extends HookConsumerWidget {
}; };
}, []); }, []);
useEffect(() { void handleFilterChange(String? filter) {
void listener() { currentFilter.value = filter;
switch (tabController.index) { notifier.applyFilter(filter);
case 0:
currentFilter.value = null;
break;
case 1:
currentFilter.value = 'subscriptions';
break;
case 2:
currentFilter.value = 'friends';
break;
} }
}
tabController.addListener(listener);
return () => tabController.removeListener(listener);
}, [tabController]);
// Listen for post creation events to refresh activities // Listen for post creation events to refresh activities
useEffect(() { useEffect(() {
final subscription = eventBus.on<PostCreatedEvent>().listen((event) { final subscription = eventBus.on<PostCreatedEvent>().listen((event) {
// Refresh all activity lists when a new post is created ref.invalidate(activityListNotifierProvider);
ref.invalidate(activityListNotifierProvider(null));
ref.invalidate(activityListNotifierProvider('subscriptions'));
ref.invalidate(activityListNotifierProvider('friends'));
}); });
return subscription.cancel; return subscription.cancel;
}, []); }, []);
@@ -147,35 +126,51 @@ class ExploreScreen extends HookConsumerWidget {
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Row( child: Row(
children: [ children: [
Expanded( Row(
child: TabBar( spacing: 8,
controller: tabController, children: [
tabAlignment: TabAlignment.start, IconButton(
isScrollable: true, onPressed: () => handleFilterChange(null),
dividerColor: Colors.transparent, icon: Icon(
labelPadding: const EdgeInsets.symmetric(horizontal: 12), Symbols.explore,
tabs: [ fill: currentFilter.value == null ? 1 : null,
Tab(
icon: Tooltip(
message: 'explore'.tr(),
child: Icon(Symbols.explore),
), ),
tooltip: 'explore'.tr(),
isSelected: currentFilter.value == null,
color:
currentFilter.value == null
? Theme.of(context).colorScheme.primary
: null,
), ),
Tab( IconButton(
icon: Tooltip( onPressed: () => handleFilterChange('subscriptions'),
message: 'exploreFilterSubscriptions'.tr(), icon: Icon(
child: Icon(Symbols.subscriptions), Symbols.subscriptions,
fill: currentFilter.value == 'subscriptions' ? 1 : null,
), ),
tooltip: 'exploreFilterSubscriptions'.tr(),
isSelected: currentFilter.value == 'subscriptions',
color:
currentFilter.value == 'subscriptions'
? Theme.of(context).colorScheme.primary
: null,
), ),
Tab( IconButton(
icon: Tooltip( onPressed: () => handleFilterChange('friends'),
message: 'exploreFilterFriends'.tr(), icon: Icon(
child: Icon(Symbols.people), Symbols.people,
fill: currentFilter.value == 'friends' ? 1 : null,
), ),
tooltip: 'exploreFilterFriends'.tr(),
isSelected: currentFilter.value == 'friends',
color:
currentFilter.value == 'friends'
? Theme.of(context).colorScheme.primary
: null,
), ),
], ],
), ),
), const Spacer(),
IconButton( IconButton(
onPressed: () { onPressed: () {
context.pushNamed('articles'); context.pushNamed('articles');
@@ -239,10 +234,13 @@ class ExploreScreen extends HookConsumerWidget {
tooltip: 'search'.tr(), tooltip: 'search'.tr(),
), ),
], ],
).padding(horizontal: 8), ).padding(horizontal: 8, vertical: 4),
); );
final appBar = isWide ? null : _buildAppBar(tabController, context); final appBar =
isWide
? null
: _buildAppBar(currentFilter.value, handleFilterChange, context);
final dragging = useState(false); final dragging = useState(false);
@@ -276,7 +274,6 @@ class ExploreScreen extends HookConsumerWidget {
query, query,
events, events,
selectedDay, selectedDay,
currentFilter.value,
) )
: _buildNarrowBody(context, ref, currentFilter.value), : _buildNarrowBody(context, ref, currentFilter.value),
), ),
@@ -315,29 +312,18 @@ class ExploreScreen extends HookConsumerWidget {
); );
} }
Widget _buildActivityList( Widget _buildActivityList(BuildContext context, WidgetRef ref) {
BuildContext context,
WidgetRef ref,
String? filter,
) {
final activitiesNotifier = ref.watch(
activityListNotifierProvider(filter).notifier,
);
final isWide = isWideScreen(context); final isWide = isWideScreen(context);
return PagingHelperSliverView( return PaginationWidget(
provider: activityListNotifierProvider(filter), provider: activityListNotifierProvider,
futureRefreshable: activityListNotifierProvider(filter).future, notifier: activityListNotifierProvider.notifier,
notifierRefreshable: activityListNotifierProvider(filter).notifier, // Sliver list cannot provide refresh handled by the pagination list
isRefreshable: false,
isSliver: true,
contentBuilder: contentBuilder:
(data, widgetCount, endItemView) => _ActivityListView( (data, footer) =>
data: data, _ActivityListView(data: data, isWide: isWide, footer: footer),
widgetCount: widgetCount,
endItemView: endItemView,
activitiesNotifier: activitiesNotifier,
isWide: isWide,
),
); );
} }
@@ -350,13 +336,10 @@ class ExploreScreen extends HookConsumerWidget {
ValueNotifier<EventCalendarQuery> query, ValueNotifier<EventCalendarQuery> query,
AsyncValue<List<dynamic>> events, AsyncValue<List<dynamic>> events,
ValueNotifier<DateTime> selectedDay, ValueNotifier<DateTime> selectedDay,
String? currentFilter,
) { ) {
final bodyView = _buildActivityList(context, ref, currentFilter); final bodyView = _buildActivityList(context, ref);
final activitiesNotifier = ref.watch( final notifier = ref.watch(activityListNotifierProvider.notifier);
activityListNotifierProvider(currentFilter).notifier,
);
return Row( return Row(
spacing: 12, spacing: 12,
@@ -364,7 +347,7 @@ class ExploreScreen extends HookConsumerWidget {
Flexible( Flexible(
flex: 3, flex: 3,
child: ExtendedRefreshIndicator( child: ExtendedRefreshIndicator(
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), onRefresh: notifier.refresh,
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
const SliverGap(12), const SliverGap(12),
@@ -449,54 +432,57 @@ class ExploreScreen extends HookConsumerWidget {
} }
PreferredSizeWidget _buildAppBar( PreferredSizeWidget _buildAppBar(
TabController tabController, String? currentFilter,
void Function(String?) handleFilterChange,
BuildContext context, BuildContext context,
) { ) {
final foregroundColor = Theme.of(context).appBarTheme.foregroundColor; final foregroundColor = Theme.of(context).appBarTheme.foregroundColor;
return AppBar( return AppBar(
toolbarHeight: 48 + 4, toolbarHeight: 48,
flexibleSpace: Container( flexibleSpace: Container(
height: 48, height: 48,
margin: EdgeInsets.only( margin: EdgeInsets.only(
left: 8, left: 8,
right: 8, right: 8,
top: 4 + MediaQuery.of(context).padding.top, top: 4 + MediaQuery.of(context).padding.top,
bottom: 4,
), ),
child: Row( child: Row(
spacing: 8,
children: [ children: [
Expanded( IconButton(
child: TabBar( onPressed: () => handleFilterChange(null),
controller: tabController, icon: Icon(
tabAlignment: TabAlignment.start, Symbols.explore,
isScrollable: true, color: foregroundColor,
dividerColor: Colors.transparent, fill: currentFilter == null ? 1 : null,
labelPadding: const EdgeInsets.symmetric(horizontal: 12),
tabs: [
Tab(
icon: Tooltip(
message: 'explore'.tr(),
child: Icon(Symbols.explore, color: foregroundColor),
), ),
tooltip: 'explore'.tr(),
isSelected: currentFilter == null,
color: currentFilter == null ? foregroundColor : null,
), ),
Tab( IconButton(
icon: Tooltip( onPressed: () => handleFilterChange('subscriptions'),
message: 'exploreFilterSubscriptions'.tr(), icon: Icon(
child: Icon(
Symbols.subscriptions, Symbols.subscriptions,
color: foregroundColor, color: foregroundColor,
fill: currentFilter == 'subscription' ? 1 : null,
), ),
tooltip: 'exploreFilterSubscriptions'.tr(),
isSelected: currentFilter == 'subscriptions',
), ),
IconButton(
onPressed: () => handleFilterChange('friends'),
icon: Icon(
Symbols.people,
color: foregroundColor,
fill: currentFilter == 'friends' ? 1 : null,
), ),
Tab( tooltip: 'exploreFilterFriends'.tr(),
icon: Tooltip( isSelected: currentFilter == 'friends',
message: 'exploreFilterFriends'.tr(),
child: Icon(Symbols.people, color: foregroundColor),
),
),
],
),
), ),
const Spacer(),
IconButton( IconButton(
onPressed: () { onPressed: () {
context.pushNamed('articles'); context.pushNamed('articles');
@@ -575,17 +561,15 @@ class ExploreScreen extends HookConsumerWidget {
notificationUnreadCountNotifierProvider, notificationUnreadCountNotifierProvider,
); );
final activitiesNotifier = ref.watch( final bodyView = _buildActivityList(context, ref);
activityListNotifierProvider(currentFilter).notifier,
);
final bodyView = _buildActivityList(context, ref, currentFilter); final notifier = ref.watch(activityListNotifierProvider.notifier);
return Expanded( return Expanded(
child: ExtendedRefreshIndicator(
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: ExtendedRefreshIndicator(
onRefresh: notifier.refresh,
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
const SliverGap(8), const SliverGap(8),
@@ -623,8 +607,8 @@ class ExploreScreen extends HookConsumerWidget {
bodyView, bodyView,
], ],
), ),
).padding(horizontal: 8),
), ),
).padding(horizontal: 8),
); );
} }
} }
@@ -741,31 +725,29 @@ class _DiscoveryActivityItem extends StatelessWidget {
} }
class _ActivityListView extends HookConsumerWidget { class _ActivityListView extends HookConsumerWidget {
final CursorPagingData<SnTimelineEvent> data; final List<SnTimelineEvent> data;
final int widgetCount;
final Widget endItemView;
final ActivityListNotifier activitiesNotifier;
final bool isWide; final bool isWide;
final Widget footer;
const _ActivityListView({ const _ActivityListView({
required this.data, required this.data,
required this.widgetCount,
required this.endItemView,
required this.activitiesNotifier,
required this.isWide, required this.isWide,
required this.footer,
}); });
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final notifier = ref.watch(activityListNotifierProvider.notifier);
return SliverList.separated( return SliverList.separated(
itemCount: widgetCount, itemCount: data.length + 1,
separatorBuilder: (_, _) => const Gap(8), separatorBuilder: (_, _) => const Gap(8),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == widgetCount - 1) { if (index == data.length) {
return endItemView; return footer;
} }
final item = data.items[index]; final item = data[index];
if (item.data == null) { if (item.data == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@@ -778,13 +760,10 @@ class _ActivityListView extends HookConsumerWidget {
borderRadius: 8, borderRadius: 8,
item: SnPost.fromJson(item.data!), item: SnPost.fromJson(item.data!),
onRefresh: () { onRefresh: () {
activitiesNotifier.forceRefresh(); notifier.refresh();
}, },
onUpdate: (post) { onUpdate: (post) {
activitiesNotifier.updateOne( notifier.updateOne(index, item.copyWith(data: post.toJson()));
index,
item.copyWith(data: post.toJson()),
);
}, },
); );
itemWidget = Card(margin: EdgeInsets.zero, child: itemWidget); itemWidget = Card(margin: EdgeInsets.zero, child: itemWidget);
@@ -801,69 +780,3 @@ class _ActivityListView extends HookConsumerWidget {
); );
} }
} }
@riverpod
class ActivityListNotifier extends _$ActivityListNotifier
with CursorPagingNotifierMixin<SnTimelineEvent> {
@override
Future<CursorPagingData<SnTimelineEvent>> build(String? filter) =>
fetch(cursor: null);
@override
Future<CursorPagingData<SnTimelineEvent>> fetch({
required String? cursor,
}) async {
final client = ref.read(apiClientProvider);
final take = 20;
final queryParameters = {
if (cursor != null) 'cursor': cursor,
'take': take,
if (filter != null) 'filter': filter,
if (kDebugMode)
'debugInclude': 'realms,publishers,articles,shuffledPosts',
};
final response = await client.get(
'/sphere/timeline',
queryParameters: queryParameters,
);
final List<SnTimelineEvent> items =
(response.data as List)
.map((e) => SnTimelineEvent.fromJson(e as Map<String, dynamic>))
.toList();
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
final nextCursor =
items.isNotEmpty
? items
.map((x) => x.createdAt)
.reduce((a, b) => a.isBefore(b) ? a : b)
.toUtc()
.toIso8601String()
: null;
return CursorPagingData(
items: items,
hasMore: hasMore,
nextCursor: nextCursor,
);
}
void updateOne(int index, SnTimelineEvent activity) {
final currentState = state.valueOrNull;
if (currentState == null) return;
final updatedItems = [...currentState.items];
updatedItems[index] = activity;
state = AsyncData(
CursorPagingData(
items: updatedItems,
hasMore: currentState.hasMore,
nextCursor: currentState.nextCursor,
),
);
}
}

View File

@@ -1,181 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'explore.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$activityListNotifierHash() =>
r'77ffc7852feffa5438b56fa26123d453b7c310cf';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$ActivityListNotifier
extends
BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnTimelineEvent>> {
late final String? filter;
FutureOr<CursorPagingData<SnTimelineEvent>> build(String? filter);
}
/// See also [ActivityListNotifier].
@ProviderFor(ActivityListNotifier)
const activityListNotifierProvider = ActivityListNotifierFamily();
/// See also [ActivityListNotifier].
class ActivityListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnTimelineEvent>>> {
/// See also [ActivityListNotifier].
const ActivityListNotifierFamily();
/// See also [ActivityListNotifier].
ActivityListNotifierProvider call(String? filter) {
return ActivityListNotifierProvider(filter);
}
@override
ActivityListNotifierProvider getProviderOverride(
covariant ActivityListNotifierProvider provider,
) {
return call(provider.filter);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'activityListNotifierProvider';
}
/// See also [ActivityListNotifier].
class ActivityListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
ActivityListNotifier,
CursorPagingData<SnTimelineEvent>
> {
/// See also [ActivityListNotifier].
ActivityListNotifierProvider(String? filter)
: this._internal(
() => ActivityListNotifier()..filter = filter,
from: activityListNotifierProvider,
name: r'activityListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$activityListNotifierHash,
dependencies: ActivityListNotifierFamily._dependencies,
allTransitiveDependencies:
ActivityListNotifierFamily._allTransitiveDependencies,
filter: filter,
);
ActivityListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.filter,
}) : super.internal();
final String? filter;
@override
FutureOr<CursorPagingData<SnTimelineEvent>> runNotifierBuild(
covariant ActivityListNotifier notifier,
) {
return notifier.build(filter);
}
@override
Override overrideWith(ActivityListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: ActivityListNotifierProvider._internal(
() => create()..filter = filter,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
filter: filter,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
ActivityListNotifier,
CursorPagingData<SnTimelineEvent>
>
createElement() {
return _ActivityListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ActivityListNotifierProvider && other.filter == filter;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, filter.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ActivityListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnTimelineEvent>> {
/// The parameter `filter` of this provider.
String? get filter;
}
class _ActivityListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
ActivityListNotifier,
CursorPagingData<SnTimelineEvent>
>
with ActivityListNotifierRef {
_ActivityListNotifierProviderElement(super.provider);
@override
String? get filter => (origin as ActivityListNotifierProvider).filter;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -116,7 +116,7 @@ class FileListScreen extends HookConsumerWidget {
completer.future completer.future
.then((uploadedFile) { .then((uploadedFile) {
if (uploadedFile != null) { if (uploadedFile != null) {
ref.invalidate(cloudFileListNotifierProvider); ref.invalidate(indexedCloudFileListNotifierProvider);
} }
}) })
.catchError((error) { .catchError((error) {

View File

@@ -1,60 +1,72 @@
import 'dart:math' as math;
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_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/sticker.dart'; import 'package:island/models/sticker.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/paging.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'dart:async'; import 'dart:async';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
part 'sticker_marketplace.g.dart'; part 'sticker_marketplace.freezed.dart';
@riverpod @freezed
class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier sealed class MarketplaceStickerQuery with _$MarketplaceStickerQuery {
with CursorPagingNotifierMixin<SnStickerPack> { const factory MarketplaceStickerQuery({
@override
Future<CursorPagingData<SnStickerPack>> build({
required String? query,
required bool byUsage, required bool byUsage,
}) { required String? query,
return fetch(cursor: null); }) = _MarketplaceStickerQuery;
} }
final marketplaceStickerPacksNotifierProvider = AsyncNotifierProvider(
MarketplaceStickerPacksNotifier.new,
);
class MarketplaceStickerPacksNotifier extends AsyncNotifier<List<SnStickerPack>>
with
AsyncPaginationController<SnStickerPack>,
AsyncPaginationFilter<MarketplaceStickerQuery, SnStickerPack> {
static const int pageSize = 20;
@override @override
Future<CursorPagingData<SnStickerPack>> fetch({ MarketplaceStickerQuery currentFilter = MarketplaceStickerQuery(
required String? cursor, byUsage: true,
}) async { query: null,
);
@override
Future<List<SnStickerPack>> fetch() async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final offset = cursor == null ? 0 : int.parse(cursor);
final response = await client.get( final response = await client.get(
'/sphere/stickers', '/sphere/stickers',
queryParameters: { queryParameters: {
'offset': offset, 'offset': fetchedCount.toString(),
'take': 20, 'take': pageSize,
'order': byUsage ? 'usage' : 'date', 'order': currentFilter.byUsage ? 'usage' : 'date',
if (query != null && query!.isNotEmpty) 'query': query, if (currentFilter.query != null && currentFilter.query!.isNotEmpty)
'query': currentFilter.query,
}, },
); );
final total = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final List<dynamic> data = response.data; final stickers =
final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList(); response.data
.map((e) => SnStickerPack.fromJson(e))
.cast<SnStickerPack>()
.toList();
final hasMore = offset + stickers.length < total; return stickers;
final nextCursor = hasMore ? (offset + stickers.length).toString() : null;
return CursorPagingData(
items: stickers,
hasMore: hasMore,
nextCursor: nextCursor,
);
} }
} }
@@ -65,17 +77,23 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final byUsage = useState(true); final query = useState<MarketplaceStickerQuery>(
final query = useState<String?>(null); MarketplaceStickerQuery(byUsage: true, query: null),
);
final searchController = useTextEditingController(); final searchController = useTextEditingController();
final focusNode = useFocusNode(); final focusNode = useFocusNode();
final debounceTimer = useState<Timer?>(null); final debounceTimer = useState<Timer?>(null);
final notifier = ref.watch(
marketplaceStickerPacksNotifierProvider.notifier,
);
// Clear search when query is cleared // Clear search when query is cleared
useEffect(() { useEffect(() {
if (query.value == null || query.value!.isEmpty) { if (query.value.query == null || query.value.query!.isEmpty) {
searchController.clear(); searchController.clear();
} }
notifier.applyFilter(query.value);
return null; return null;
}, [query]); }, [query]);
@@ -92,41 +110,24 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
actions: [ actions: [
IconButton( IconButton(
onPressed: () { onPressed: () {
byUsage.value = !byUsage.value; query.value = query.value.copyWith(byUsage: !query.value.byUsage);
}, },
icon: icon:
byUsage.value query.value.byUsage
? const Icon(Symbols.local_fire_department) ? const Icon(Symbols.local_fire_department)
: const Icon(Symbols.access_time), : const Icon(Symbols.access_time),
tooltip: tooltip:
byUsage.value query.value.byUsage
? 'orderByPopularity'.tr() ? 'orderByPopularity'.tr()
: 'orderByReleaseDate'.tr(), : 'orderByReleaseDate'.tr(),
), ),
const Gap(8), const Gap(8),
], ],
), ),
body: PagingHelperView( body: Column(
provider: marketplaceStickerPacksNotifierProvider(
byUsage: byUsage.value,
query: query.value,
),
futureRefreshable:
marketplaceStickerPacksNotifierProvider(
byUsage: byUsage.value,
query: query.value,
).future,
notifierRefreshable:
marketplaceStickerPacksNotifierProvider(
byUsage: byUsage.value,
query: query.value,
).notifier,
contentBuilder:
(data, widgetCount, endItemView) => Column(
children: [ children: [
// Search bar above the list
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: SearchBar( child: SearchBar(
elevation: WidgetStateProperty.all(4), elevation: WidgetStateProperty.all(4),
controller: searchController, controller: searchController,
@@ -139,11 +140,11 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
onTapOutside: onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(), (_) => FocusManager.instance.primaryFocus?.unfocus(),
trailing: [ trailing: [
if (query.value != null && query.value!.isNotEmpty) if (query.value.query != null && query.value.query!.isNotEmpty)
IconButton( IconButton(
icon: const Icon(Symbols.close), icon: const Icon(Symbols.close),
onPressed: () { onPressed: () {
query.value = null; query.value = query.value.copyWith(query: null);
searchController.clear(); searchController.clear();
focusNode.unfocus(); focusNode.unfocus();
}, },
@@ -155,27 +156,121 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
debounceTimer.value = Timer( debounceTimer.value = Timer(
const Duration(milliseconds: 500), const Duration(milliseconds: 500),
() { () {
query.value = value.isEmpty ? null : value; query.value = query.value.copyWith(query: value);
}, },
); );
}, },
onSubmitted: (value) { onSubmitted: (value) {
query.value = value.isEmpty ? null : value; query.value = query.value.copyWith(query: value);
focusNode.unfocus(); focusNode.unfocus();
}, },
), ),
), ),
Expanded( Expanded(
child: ListView.builder( child: PaginationList(
padding: EdgeInsets.zero, padding: EdgeInsets.only(top: 8),
itemCount: widgetCount, provider: marketplaceStickerPacksNotifierProvider,
itemBuilder: (context, index) { notifier: marketplaceStickerPacksNotifierProvider.notifier,
if (index == widgetCount - 1) { itemBuilder:
return endItemView; (context, idx, pack) => Card(
} margin: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Column(
final pack = data.items[index]; children: [
return ListTile( Container(
color:
Theme.of(context).colorScheme.secondaryContainer,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 20,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
math.min(pack.stickers.length, 4),
(index) => Padding(
padding: EdgeInsets.only(
right: index < 3 ? 8 : 0,
),
child: Container(
constraints: const BoxConstraints(
maxWidth: 80,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
8,
),
color:
Theme.of(
context,
).colorScheme.tertiaryContainer,
),
child: CloudImageWidget(
file: pack.stickers[index].image,
),
).clipRRect(all: 8),
),
),
),
if (pack.stickers.length > 4)
const SizedBox(height: 8),
if (pack.stickers.length > 4)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
math.min(pack.stickers.length - 4, 4),
(index) => Padding(
padding: EdgeInsets.only(
right: index < 3 ? 8 : 0,
),
child: Container(
constraints: const BoxConstraints(
maxWidth: 80,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
8,
),
color:
Theme.of(
context,
).colorScheme.tertiaryContainer,
),
child: CloudImageWidget(
file:
pack.stickers[index + 4].image,
),
).clipRRect(all: 8),
),
),
),
],
),
),
).clipRRect(topLeft: 8, topRight: 8),
ListTile(
leading: Container(
decoration: BoxDecoration(
color:
Theme.of(
context,
).colorScheme.tertiaryContainer,
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
),
child: CloudImageWidget(
file: pack.icon ?? pack.stickers.first.image,
),
).width(40).height(40).clipRRect(all: 8),
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
),
title: Text(pack.name), title: Text(pack.name),
subtitle: Text(pack.description), subtitle: Text(pack.description),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
@@ -187,13 +282,14 @@ class MarketplaceStickersScreen extends HookConsumerWidget {
pathParameters: {'packId': pack.id}, pathParameters: {'packId': pack.id},
); );
}, },
);
},
),
), ),
], ],
), ),
), ),
),
),
],
),
); );
} }
} }

View File

@@ -0,0 +1,268 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'sticker_marketplace.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$MarketplaceStickerQuery {
bool get byUsage; String? get query;
/// Create a copy of MarketplaceStickerQuery
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$MarketplaceStickerQueryCopyWith<MarketplaceStickerQuery> get copyWith => _$MarketplaceStickerQueryCopyWithImpl<MarketplaceStickerQuery>(this as MarketplaceStickerQuery, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MarketplaceStickerQuery&&(identical(other.byUsage, byUsage) || other.byUsage == byUsage)&&(identical(other.query, query) || other.query == query));
}
@override
int get hashCode => Object.hash(runtimeType,byUsage,query);
@override
String toString() {
return 'MarketplaceStickerQuery(byUsage: $byUsage, query: $query)';
}
}
/// @nodoc
abstract mixin class $MarketplaceStickerQueryCopyWith<$Res> {
factory $MarketplaceStickerQueryCopyWith(MarketplaceStickerQuery value, $Res Function(MarketplaceStickerQuery) _then) = _$MarketplaceStickerQueryCopyWithImpl;
@useResult
$Res call({
bool byUsage, String? query
});
}
/// @nodoc
class _$MarketplaceStickerQueryCopyWithImpl<$Res>
implements $MarketplaceStickerQueryCopyWith<$Res> {
_$MarketplaceStickerQueryCopyWithImpl(this._self, this._then);
final MarketplaceStickerQuery _self;
final $Res Function(MarketplaceStickerQuery) _then;
/// Create a copy of MarketplaceStickerQuery
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? byUsage = null,Object? query = freezed,}) {
return _then(_self.copyWith(
byUsage: null == byUsage ? _self.byUsage : byUsage // ignore: cast_nullable_to_non_nullable
as bool,query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [MarketplaceStickerQuery].
extension MarketplaceStickerQueryPatterns on MarketplaceStickerQuery {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _MarketplaceStickerQuery value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _MarketplaceStickerQuery() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _MarketplaceStickerQuery value) $default,){
final _that = this;
switch (_that) {
case _MarketplaceStickerQuery():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _MarketplaceStickerQuery value)? $default,){
final _that = this;
switch (_that) {
case _MarketplaceStickerQuery() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool byUsage, String? query)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MarketplaceStickerQuery() when $default != null:
return $default(_that.byUsage,_that.query);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool byUsage, String? query) $default,) {final _that = this;
switch (_that) {
case _MarketplaceStickerQuery():
return $default(_that.byUsage,_that.query);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool byUsage, String? query)? $default,) {final _that = this;
switch (_that) {
case _MarketplaceStickerQuery() when $default != null:
return $default(_that.byUsage,_that.query);case _:
return null;
}
}
}
/// @nodoc
class _MarketplaceStickerQuery implements MarketplaceStickerQuery {
const _MarketplaceStickerQuery({required this.byUsage, required this.query});
@override final bool byUsage;
@override final String? query;
/// Create a copy of MarketplaceStickerQuery
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$MarketplaceStickerQueryCopyWith<_MarketplaceStickerQuery> get copyWith => __$MarketplaceStickerQueryCopyWithImpl<_MarketplaceStickerQuery>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MarketplaceStickerQuery&&(identical(other.byUsage, byUsage) || other.byUsage == byUsage)&&(identical(other.query, query) || other.query == query));
}
@override
int get hashCode => Object.hash(runtimeType,byUsage,query);
@override
String toString() {
return 'MarketplaceStickerQuery(byUsage: $byUsage, query: $query)';
}
}
/// @nodoc
abstract mixin class _$MarketplaceStickerQueryCopyWith<$Res> implements $MarketplaceStickerQueryCopyWith<$Res> {
factory _$MarketplaceStickerQueryCopyWith(_MarketplaceStickerQuery value, $Res Function(_MarketplaceStickerQuery) _then) = __$MarketplaceStickerQueryCopyWithImpl;
@override @useResult
$Res call({
bool byUsage, String? query
});
}
/// @nodoc
class __$MarketplaceStickerQueryCopyWithImpl<$Res>
implements _$MarketplaceStickerQueryCopyWith<$Res> {
__$MarketplaceStickerQueryCopyWithImpl(this._self, this._then);
final _MarketplaceStickerQuery _self;
final $Res Function(_MarketplaceStickerQuery) _then;
/// Create a copy of MarketplaceStickerQuery
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? byUsage = null,Object? query = freezed,}) {
return _then(_MarketplaceStickerQuery(
byUsage: null == byUsage ? _self.byUsage : byUsage // ignore: cast_nullable_to_non_nullable
as bool,query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -1,213 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'sticker_marketplace.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$marketplaceStickerPacksNotifierHash() =>
r'3bde76e18bb024f45ff6261fe735cdba97b02808';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$MarketplaceStickerPacksNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> {
late final String? query;
late final bool byUsage;
FutureOr<CursorPagingData<SnStickerPack>> build({
required String? query,
required bool byUsage,
});
}
/// See also [MarketplaceStickerPacksNotifier].
@ProviderFor(MarketplaceStickerPacksNotifier)
const marketplaceStickerPacksNotifierProvider =
MarketplaceStickerPacksNotifierFamily();
/// See also [MarketplaceStickerPacksNotifier].
class MarketplaceStickerPacksNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnStickerPack>>> {
/// See also [MarketplaceStickerPacksNotifier].
const MarketplaceStickerPacksNotifierFamily();
/// See also [MarketplaceStickerPacksNotifier].
MarketplaceStickerPacksNotifierProvider call({
required String? query,
required bool byUsage,
}) {
return MarketplaceStickerPacksNotifierProvider(
query: query,
byUsage: byUsage,
);
}
@override
MarketplaceStickerPacksNotifierProvider getProviderOverride(
covariant MarketplaceStickerPacksNotifierProvider provider,
) {
return call(query: provider.query, byUsage: provider.byUsage);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'marketplaceStickerPacksNotifierProvider';
}
/// See also [MarketplaceStickerPacksNotifier].
class MarketplaceStickerPacksNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
MarketplaceStickerPacksNotifier,
CursorPagingData<SnStickerPack>
> {
/// See also [MarketplaceStickerPacksNotifier].
MarketplaceStickerPacksNotifierProvider({
required String? query,
required bool byUsage,
}) : this._internal(
() =>
MarketplaceStickerPacksNotifier()
..query = query
..byUsage = byUsage,
from: marketplaceStickerPacksNotifierProvider,
name: r'marketplaceStickerPacksNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$marketplaceStickerPacksNotifierHash,
dependencies: MarketplaceStickerPacksNotifierFamily._dependencies,
allTransitiveDependencies:
MarketplaceStickerPacksNotifierFamily._allTransitiveDependencies,
query: query,
byUsage: byUsage,
);
MarketplaceStickerPacksNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.query,
required this.byUsage,
}) : super.internal();
final String? query;
final bool byUsage;
@override
FutureOr<CursorPagingData<SnStickerPack>> runNotifierBuild(
covariant MarketplaceStickerPacksNotifier notifier,
) {
return notifier.build(query: query, byUsage: byUsage);
}
@override
Override overrideWith(MarketplaceStickerPacksNotifier Function() create) {
return ProviderOverride(
origin: this,
override: MarketplaceStickerPacksNotifierProvider._internal(
() =>
create()
..query = query
..byUsage = byUsage,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
query: query,
byUsage: byUsage,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
MarketplaceStickerPacksNotifier,
CursorPagingData<SnStickerPack>
>
createElement() {
return _MarketplaceStickerPacksNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is MarketplaceStickerPacksNotifierProvider &&
other.query == query &&
other.byUsage == byUsage;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, query.hashCode);
hash = _SystemHash.combine(hash, byUsage.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin MarketplaceStickerPacksNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnStickerPack>> {
/// The parameter `query` of this provider.
String? get query;
/// The parameter `byUsage` of this provider.
bool get byUsage;
}
class _MarketplaceStickerPacksNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
MarketplaceStickerPacksNotifier,
CursorPagingData<SnStickerPack>
>
with MarketplaceStickerPacksNotifierRef {
_MarketplaceStickerPacksNotifierProviderElement(super.provider);
@override
String? get query =>
(origin as MarketplaceStickerPacksNotifierProvider).query;
@override
bool get byUsage =>
(origin as MarketplaceStickerPacksNotifierProvider).byUsage;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -2,6 +2,7 @@ import 'dart:convert';
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:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
@@ -11,6 +12,7 @@ import 'package:island/services/udid.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/response.dart'; import 'package:island/widgets/response.dart';
import 'package:island/widgets/sites/info_row.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@@ -19,28 +21,30 @@ import 'package:island/widgets/extended_refresh_indicator.dart';
part 'account_devices.g.dart'; part 'account_devices.g.dart';
@riverpod @riverpod
Future<List<SnAuthDeviceWithChallenge>> authDevices(Ref ref) async { Future<List<SnAuthDeviceWithSession>> authDevices(Ref ref) async {
final resp = await ref final resp = await ref
.watch(apiClientProvider) .watch(apiClientProvider)
.get('/pass/accounts/me/devices'); .get('/pass/accounts/me/devices');
final currentId = await getUdid(); final currentId = await getUdid();
final data = final data =
resp.data.map<SnAuthDeviceWithChallenge>((e) { resp.data.map<SnAuthDeviceWithSession>((e) {
final ele = SnAuthDeviceWithChallenge.fromJson(e); final ele = SnAuthDeviceWithSession.fromJson(e);
return ele.copyWith(isCurrent: ele.deviceId == currentId); return ele.copyWith(isCurrent: ele.deviceId == currentId);
}).toList(); }).toList();
return data; return data;
} }
class _DeviceListTile extends StatelessWidget { class _DeviceListTile extends StatelessWidget {
final SnAuthDeviceWithChallenge device; final SnAuthDeviceWithSession device;
final Function(String) updateDeviceLabel; final Function(String) updateDeviceLabel;
final Function(String) logoutDevice; final Function(String) logoutDevice;
final Function(String) logoutSession;
const _DeviceListTile({ const _DeviceListTile({
required this.device, required this.device,
required this.updateDeviceLabel, required this.updateDeviceLabel,
required this.logoutDevice, required this.logoutDevice,
required this.logoutSession,
}); });
@override @override
@@ -69,9 +73,10 @@ class _DeviceListTile extends StatelessWidget {
subtitle: Column( subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
if (device.sessions.isNotEmpty)
Text( Text(
'lastActiveAt'.tr( 'lastActiveAt'.tr(
args: [device.challenges.first.createdAt.formatSystem()], args: [device.sessions.first.createdAt.formatSystem()],
), ),
), ),
], ],
@@ -114,26 +119,55 @@ class _DeviceListTile extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text('authDeviceChallenges'.tr()), child: Text('authDeviceChallenges'.tr()),
), ),
for (final challenge in device.challenges) ...device.sessions
ListTile( .map(
minTileHeight: 48, (session) => Row(
title: Text(DateFormat().format(challenge.createdAt.toLocal())), crossAxisAlignment: CrossAxisAlignment.start,
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text(challenge.ipAddress), Expanded(
if (challenge.location != null) child: Column(
Row( crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4, spacing: 4,
children: children: [
[challenge.location?.city, challenge.location?.country] InfoRow(
.where((e) => e?.isNotEmpty ?? false) label: 'createdAt'.tr(
.map((e) => Text(e!)) args: [session.createdAt.toLocal().formatSystem()],
.toList(), ),
icon: Symbols.join,
),
InfoRow(
label: 'lastActiveAt'.tr(
args: [
session.lastGrantedAt.toLocal().formatSystem(),
],
),
icon: Symbols.refresh_rounded,
),
InfoRow(
label:
'${'location'.tr()} ${session.location?.city ?? 'unknown'.tr()}',
icon: Symbols.pin_drop,
),
InfoRow(
label:
'${'ipAddress'.tr()} ${session.ipAddress ?? 'unknown'.tr()}',
icon: Symbols.dns,
), ),
], ],
), ),
), ),
IconButton(
icon: Icon(Icons.logout),
tooltip: 'authSessionLogout'.tr(),
onPressed: () => logoutSession(session.id),
),
const Gap(4),
],
).padding(horizontal: 20, vertical: 8),
)
.expand((element) => [element, const Divider(height: 1)])
.toList()
..removeLast(),
], ],
); );
} }
@@ -162,6 +196,22 @@ class AccountSessionSheet extends HookConsumerWidget {
} }
} }
void logoutSession(String sessionId) async {
final confirm = await showConfirmAlert(
'authSessionLogoutHint'.tr(),
'authSessionLogout'.tr(),
isDanger: true,
);
if (!confirm || !context.mounted) return;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete('/pass/accounts/me/sessions/$sessionId');
ref.invalidate(authDevicesProvider);
} catch (err) {
showErrorAlert(err);
}
}
void updateDeviceLabel(String sessionId) async { void updateDeviceLabel(String sessionId) async {
final controller = TextEditingController(); final controller = TextEditingController();
final label = await showDialog<String>( final label = await showDialog<String>(
@@ -249,6 +299,7 @@ class AccountSessionSheet extends HookConsumerWidget {
device: device, device: device,
updateDeviceLabel: updateDeviceLabel, updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice, logoutDevice: logoutDevice,
logoutSession: logoutSession,
); );
} else { } else {
return Dismissible( return Dismissible(
@@ -304,6 +355,7 @@ class AccountSessionSheet extends HookConsumerWidget {
device: device, device: device,
updateDeviceLabel: updateDeviceLabel, updateDeviceLabel: updateDeviceLabel,
logoutDevice: logoutDevice, logoutDevice: logoutDevice,
logoutSession: logoutSession,
), ),
); );
} }

View File

@@ -6,12 +6,12 @@ part of 'account_devices.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$authDevicesHash() => r'35735af4ed75b73fe80c8942e53b3bc26a569c01'; String _$authDevicesHash() => r'1af378149286020ec263be178c573ccc247a0cd1';
/// See also [authDevices]. /// See also [authDevices].
@ProviderFor(authDevices) @ProviderFor(authDevices)
final authDevicesProvider = final authDevicesProvider =
AutoDisposeFutureProvider<List<SnAuthDeviceWithChallenge>>.internal( AutoDisposeFutureProvider<List<SnAuthDeviceWithSession>>.internal(
authDevices, authDevices,
name: r'authDevicesProvider', name: r'authDevicesProvider',
debugGetCreateSourceHash: debugGetCreateSourceHash:
@@ -25,6 +25,6 @@ final authDevicesProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef AuthDevicesRef = typedef AuthDevicesRef =
AutoDisposeFutureProviderRef<List<SnAuthDeviceWithChallenge>>; AutoDisposeFutureProviderRef<List<SnAuthDeviceWithSession>>;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -17,6 +17,7 @@ import "package:island/models/wallet.dart";
import "package:island/models/realm.dart"; import "package:island/models/realm.dart";
import "package:island/models/sticker.dart"; import "package:island/models/sticker.dart";
import "package:island/pods/config.dart"; import "package:island/pods/config.dart";
import "package:island/pods/userinfo.dart";
import "package:island/services/autocomplete_service.dart"; import "package:island/services/autocomplete_service.dart";
import "package:island/services/responsive.dart"; import "package:island/services/responsive.dart";
import "package:island/widgets/content/attachment_preview.dart"; import "package:island/widgets/content/attachment_preview.dart";
@@ -344,6 +345,14 @@ class ChatInput extends HookConsumerWidget {
final double rightMargin = isWideScreen(context) ? leftMargin + 8 : 16; final double rightMargin = isWideScreen(context) ? leftMargin + 8 : 16;
const double bottomMargin = 16; const double bottomMargin = 16;
final userInfo = ref.watch(userInfoProvider);
List<SnChatMember> getValidMembers(List<SnChatMember> members) {
return members
.where((member) => member.accountId != userInfo.value?.id)
.toList();
}
return Container( return Container(
margin: EdgeInsets.only( margin: EdgeInsets.only(
left: leftMargin, left: leftMargin,
@@ -878,9 +887,9 @@ class ChatInput extends HookConsumerWidget {
(chatRoom.type == 1 && chatRoom.name == null) (chatRoom.type == 1 && chatRoom.name == null)
? 'chatDirectMessageHint'.tr( ? 'chatDirectMessageHint'.tr(
args: [ args: [
chatRoom.members! getValidMembers(
.map((e) => e.account.nick) chatRoom.members!,
.join(', '), ).map((e) => e.account.nick).join(', '),
], ],
) )
: 'chatMessageHint'.tr( : 'chatMessageHint'.tr(

View File

@@ -9,7 +9,7 @@ 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';
import 'package:island/database/message.dart'; import 'package:island/database/message.dart';
import 'package:island/pods/chat/chat_rooms.dart'; import 'package:island/pods/chat/chat_room.dart';
import 'package:island/pods/chat/messages_notifier.dart'; import 'package:island/pods/chat/messages_notifier.dart';
import 'package:island/pods/translate.dart'; import 'package:island/pods/translate.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';

View File

@@ -6,6 +6,7 @@ import "package:gap/gap.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:island/database/message.dart"; import "package:island/database/message.dart";
import "package:island/models/chat.dart"; import "package:island/models/chat.dart";
import "package:island/pods/chat/chat_room.dart";
import "package:island/pods/chat/messages_notifier.dart"; import "package:island/pods/chat/messages_notifier.dart";
import "package:island/pods/network.dart"; import "package:island/pods/network.dart";
import "package:island/services/responsive.dart"; import "package:island/services/responsive.dart";
@@ -19,8 +20,6 @@ import "package:styled_widget/styled_widget.dart";
import "package:super_sliver_list/super_sliver_list.dart"; import "package:super_sliver_list/super_sliver_list.dart";
import "package:material_symbols_icons/symbols.dart"; import "package:material_symbols_icons/symbols.dart";
import "package:island/screens/chat/chat.dart";
class PublicRoomPreview extends HookConsumerWidget { class PublicRoomPreview extends HookConsumerWidget {
final String id; final String id;
final SnChatRoom room; final SnChatRoom room;
@@ -205,7 +204,7 @@ class PublicRoomPreview extends HookConsumerWidget {
showLoadingModal(context); showLoadingModal(context);
final apiClient = ref.read(apiClientProvider); final apiClient = ref.read(apiClientProvider);
await apiClient.post('/sphere/chat/${room.id}/members/me'); await apiClient.post('/sphere/chat/${room.id}/members/me');
ref.invalidate(chatroomIdentityProvider(id)); ref.invalidate(ChatRoomIdentityNotifierProvider(id));
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {

View File

@@ -1,4 +1,5 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:dismissible_page/dismissible_page.dart';
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:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -14,12 +15,14 @@ import 'package:island/screens/account/profile.dart';
import 'package:island/screens/creators/publishers_form.dart'; import 'package:island/screens/creators/publishers_form.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/cloud_file_lightbox.dart';
import 'package:island/widgets/content/markdown_latex.dart'; import 'package:island/widgets/content/markdown_latex.dart';
import 'package:markdown/markdown.dart' as markdown; import 'package:markdown/markdown.dart' as markdown;
import 'package:markdown_widget/markdown_widget.dart'; import 'package:markdown_widget/markdown_widget.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
import 'image.dart'; import 'image.dart';
@@ -185,20 +188,34 @@ class MarkdownTextContent extends HookConsumerWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return ClipRRect( final heroTag = 'cloud-file-markdown#${const Uuid().v4()}';
return InkWell(
onTap: () {
context.pushTransparentRoute(
CloudFileLightbox(item: file, heroTag: heroTag),
rootNavigator: true,
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer, color:
Theme.of(context).colorScheme.surfaceContainer,
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(8), Radius.circular(8),
), ),
), ),
child: CloudFileWidget( child: CloudFileWidget(
item: file, item: file,
heroTag: heroTag,
fit: BoxFit.cover, fit: BoxFit.cover,
).clipRRect(all: 8), ).clipRRect(all: 8),
), ),
),
); );
} }
} }

View File

@@ -22,9 +22,9 @@ import 'package:island/utils/format.dart';
import 'package:island/utils/text.dart'; import 'package:island/utils/text.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/paging/pagination_list.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
enum FileListMode { normal, unindexed } enum FileListMode { normal, unindexed }
@@ -59,7 +59,9 @@ class FileListView extends HookConsumerWidget {
useEffect(() { useEffect(() {
if (mode.value == FileListMode.normal) { if (mode.value == FileListMode.normal) {
final notifier = ref.read(cloudFileListNotifierProvider.notifier); final notifier = ref.read(
indexedCloudFileListNotifierProvider.notifier,
);
notifier.setPath(currentPath.value); notifier.setPath(currentPath.value);
} }
return null; return null;
@@ -70,7 +72,9 @@ class FileListView extends HookConsumerWidget {
final unindexedNotifier = ref.read( final unindexedNotifier = ref.read(
unindexedFileListNotifierProvider.notifier, unindexedFileListNotifierProvider.notifier,
); );
final cloudNotifier = ref.read(cloudFileListNotifierProvider.notifier); final cloudNotifier = ref.read(
indexedCloudFileListNotifierProvider.notifier,
);
final recycled = useState<bool>(false); final recycled = useState<bool>(false);
final poolsAsync = ref.watch(poolsProvider); final poolsAsync = ref.watch(poolsProvider);
final isSelectionMode = useState<bool>(false); final isSelectionMode = useState<bool>(false);
@@ -115,49 +119,50 @@ class FileListView extends HookConsumerWidget {
final isRefreshing = ref.watch( final isRefreshing = ref.watch(
mode.value == FileListMode.normal mode.value == FileListMode.normal
? cloudFileListNotifierProvider.select((value) => value.isLoading) ? indexedCloudFileListNotifierProvider.select(
(value) => value.isLoading,
)
: unindexedFileListNotifierProvider.select( : unindexedFileListNotifierProvider.select(
(value) => value.isLoading, (value) => value.isLoading,
), ),
); );
final bodyWidget = switch (mode.value) { final bodyWidget = switch (mode.value) {
FileListMode.unindexed => PagingHelperSliverView( FileListMode.unindexed => PaginationWidget(
provider: unindexedFileListNotifierProvider, provider: unindexedFileListNotifierProvider,
futureRefreshable: unindexedFileListNotifierProvider.future, notifier: unindexedFileListNotifierProvider.notifier,
notifierRefreshable: unindexedFileListNotifierProvider.notifier, isRefreshable: false,
isSliver: true,
contentBuilder: contentBuilder:
(data, widgetCount, endItemView) => (data, footer) =>
data.items.isEmpty data.isEmpty
? SliverToBoxAdapter( ? SliverToBoxAdapter(
child: _buildEmptyUnindexedFilesHint(ref), child: _buildEmptyUnindexedFilesHint(ref),
) )
: _buildUnindexedFileListContent( : _buildUnindexedFileListContent(
data.items, data,
widgetCount,
endItemView,
ref, ref,
context, context,
viewMode, viewMode,
isSelectionMode, isSelectionMode,
selectedFileIds, selectedFileIds,
currentVisibleItems, currentVisibleItems,
footer,
), ),
), ),
_ => PagingHelperSliverView( _ => PaginationWidget(
provider: cloudFileListNotifierProvider, provider: indexedCloudFileListNotifierProvider,
futureRefreshable: cloudFileListNotifierProvider.future, notifier: indexedCloudFileListNotifierProvider.notifier,
notifierRefreshable: cloudFileListNotifierProvider.notifier, isRefreshable: false,
isSliver: true,
contentBuilder: contentBuilder:
(data, widgetCount, endItemView) => (data, footer) =>
data.items.isEmpty data.isEmpty
? SliverToBoxAdapter( ? SliverToBoxAdapter(
child: _buildEmptyDirectoryHint(ref, currentPath), child: _buildEmptyDirectoryHint(ref, currentPath),
) )
: _buildFileListContent( : _buildFileListContent(
data.items, data,
widgetCount,
endItemView,
ref, ref,
context, context,
currentPath, currentPath,
@@ -165,6 +170,7 @@ class FileListView extends HookConsumerWidget {
isSelectionMode, isSelectionMode,
selectedFileIds, selectedFileIds,
currentVisibleItems, currentVisibleItems,
footer,
), ),
), ),
}; };
@@ -255,7 +261,7 @@ class FileListView extends HookConsumerWidget {
completer.future completer.future
.then((uploadedFile) { .then((uploadedFile) {
if (uploadedFile != null) { if (uploadedFile != null) {
ref.invalidate(cloudFileListNotifierProvider); ref.invalidate(indexedCloudFileListNotifierProvider);
} }
}) })
.catchError((error) { .catchError((error) {
@@ -532,7 +538,7 @@ class FileListView extends HookConsumerWidget {
isSelectionMode.value = false; isSelectionMode.value = false;
ref.invalidate( ref.invalidate(
mode.value == FileListMode.normal mode.value == FileListMode.normal
? cloudFileListNotifierProvider ? indexedCloudFileListNotifierProvider
: unindexedFileListNotifierProvider, : unindexedFileListNotifierProvider,
); );
showSnackBar('Deleted $count files.'); showSnackBar('Deleted $count files.');
@@ -560,8 +566,6 @@ class FileListView extends HookConsumerWidget {
Widget _buildFileListContent( Widget _buildFileListContent(
List<FileListItem> items, List<FileListItem> items,
int widgetCount,
Widget endItemView,
WidgetRef ref, WidgetRef ref,
BuildContext context, BuildContext context,
ValueNotifier<String> currentPath, ValueNotifier<String> currentPath,
@@ -569,6 +573,7 @@ class FileListView extends HookConsumerWidget {
ValueNotifier<bool> isSelectionMode, ValueNotifier<bool> isSelectionMode,
ValueNotifier<Set<String>> selectedFileIds, ValueNotifier<Set<String>> selectedFileIds,
ValueNotifier<List<FileListItem>> currentVisibleItems, ValueNotifier<List<FileListItem>> currentVisibleItems,
Widget footer,
) { ) {
currentVisibleItems.value = items; currentVisibleItems.value = items;
return switch (currentViewMode.value) { return switch (currentViewMode.value) {
@@ -580,11 +585,10 @@ class FileListView extends HookConsumerWidget {
crossAxisSpacing: 8, crossAxisSpacing: 8,
mainAxisSpacing: 8, mainAxisSpacing: 8,
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
if (index == widgetCount - 1) { if (index == items.length) {
return endItemView; return footer;
} }
if (index > items.length) {
if (index >= items.length) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@@ -615,16 +619,15 @@ class FileListView extends HookConsumerWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
}, },
); );
}, childCount: widgetCount), }, childCount: items.length + 1),
), ),
// ListView mode // ListView mode
_ => SliverList.builder( _ => SliverList.builder(
itemCount: widgetCount, itemCount: items.length + 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == widgetCount - 1) { if (index == items.length) {
return endItemView; return footer;
} }
final item = items[index]; final item = items[index];
return item.map( return item.map(
file: file:
@@ -801,7 +804,7 @@ class FileListView extends HookConsumerWidget {
await client.delete( await client.delete(
'/drive/index/remove/${fileItem.fileIndex.id}', '/drive/index/remove/${fileItem.fileIndex.id}',
); );
ref.invalidate(cloudFileListNotifierProvider); ref.invalidate(indexedCloudFileListNotifierProvider);
} catch (e) { } catch (e) {
showSnackBar('failedToDeleteFile'.tr()); showSnackBar('failedToDeleteFile'.tr());
} finally { } finally {
@@ -1010,14 +1013,13 @@ class FileListView extends HookConsumerWidget {
Widget _buildUnindexedFileListContent( Widget _buildUnindexedFileListContent(
List<FileListItem> items, List<FileListItem> items,
int widgetCount,
Widget endItemView,
WidgetRef ref, WidgetRef ref,
BuildContext context, BuildContext context,
ValueNotifier<FileListViewMode> currentViewMode, ValueNotifier<FileListViewMode> currentViewMode,
ValueNotifier<bool> isSelectionMode, ValueNotifier<bool> isSelectionMode,
ValueNotifier<Set<String>> selectedFileIds, ValueNotifier<Set<String>> selectedFileIds,
ValueNotifier<List<FileListItem>> currentVisibleItems, ValueNotifier<List<FileListItem>> currentVisibleItems,
Widget footer,
) { ) {
currentVisibleItems.value = items; currentVisibleItems.value = items;
return switch (currentViewMode.value) { return switch (currentViewMode.value) {
@@ -1029,11 +1031,10 @@ class FileListView extends HookConsumerWidget {
crossAxisSpacing: 12, crossAxisSpacing: 12,
mainAxisSpacing: 12, mainAxisSpacing: 12,
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
if (index == widgetCount - 1) { if (index == items.length) {
return endItemView; return footer;
} }
if (index > items.length) {
if (index >= items.length) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@@ -1067,16 +1068,15 @@ class FileListView extends HookConsumerWidget {
}, },
), ),
); );
}, childCount: widgetCount), }, childCount: items.length + 1),
), ),
// ListView mode // ListView mode
_ => SliverList.builder( _ => SliverList.builder(
itemCount: widgetCount, itemCount: items.length + 1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == widgetCount - 1) { if (index == items.length) {
return endItemView; return footer;
} }
final item = items[index]; final item = items[index];
return item.map( return item.map(
file: (fileItem) { file: (fileItem) {
@@ -1168,7 +1168,7 @@ class FileListView extends HookConsumerWidget {
try { try {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
await client.delete('/drive/index/remove/${fileItem.fileIndex.id}'); await client.delete('/drive/index/remove/${fileItem.fileIndex.id}');
ref.invalidate(cloudFileListNotifierProvider); ref.invalidate(indexedCloudFileListNotifierProvider);
} catch (e) { } catch (e) {
showSnackBar('failedToDeleteFile'.tr()); showSnackBar('failedToDeleteFile'.tr());
} finally { } finally {

View File

@@ -0,0 +1,167 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/paging.dart';
import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:visibility_detector/visibility_detector.dart';
class PaginationList<T> extends HookConsumerWidget {
final ProviderListenable<AsyncValue<List<T>>> provider;
final Refreshable<PaginationController<T>> notifier;
final Widget? Function(BuildContext, int, T) itemBuilder;
final bool isRefreshable;
final bool isSliver;
final bool showDefaultWidgets;
final EdgeInsets? padding;
const PaginationList({
super.key,
required this.provider,
required this.notifier,
required this.itemBuilder,
this.isRefreshable = true,
this.isSliver = false,
this.showDefaultWidgets = true,
this.padding,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(provider);
final noti = ref.watch(notifier);
if (data.isLoading && data.valueOrNull?.isEmpty == true) {
final content = ResponseLoadingWidget();
return isSliver ? SliverFillRemaining(child: content) : content;
}
if (data.hasError) {
final content = ResponseErrorWidget(
error: data.error,
onRetry: noti.refresh,
);
return isSliver ? SliverFillRemaining(child: content) : content;
}
final listView =
isSliver
? SuperSliverList.builder(
itemCount: (data.valueOrNull?.length ?? 0) + 1,
itemBuilder: (context, idx) {
if (idx == data.valueOrNull?.length) {
return PaginationListFooter(noti: noti, data: data);
}
final entry = data.valueOrNull?[idx];
if (entry != null) return itemBuilder(context, idx, entry);
return null;
},
)
: SuperListView.builder(
padding: padding,
itemCount: (data.valueOrNull?.length ?? 0) + 1,
itemBuilder: (context, idx) {
if (idx == data.valueOrNull?.length) {
return PaginationListFooter(noti: noti, data: data);
}
final entry = data.valueOrNull?[idx];
if (entry != null) return itemBuilder(context, idx, entry);
return null;
},
);
return isRefreshable
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: listView)
: listView;
}
}
class PaginationWidget<T> extends HookConsumerWidget {
final ProviderListenable<AsyncValue<List<T>>> provider;
final Refreshable<PaginationController<T>> notifier;
final Widget Function(List<T>, Widget) contentBuilder;
final bool isRefreshable;
final bool isSliver;
final bool showDefaultWidgets;
const PaginationWidget({
super.key,
required this.provider,
required this.notifier,
required this.contentBuilder,
this.isRefreshable = true,
this.isSliver = false,
this.showDefaultWidgets = true,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(provider);
final noti = ref.watch(notifier);
if (data.isLoading && data.valueOrNull?.isEmpty == true) {
final content = ResponseLoadingWidget();
return isSliver ? SliverFillRemaining(child: content) : content;
}
if (data.hasError) {
final content = ResponseErrorWidget(
error: data.error,
onRetry: noti.refresh,
);
return isSliver ? SliverFillRemaining(child: content) : content;
}
final footer = PaginationListFooter(noti: noti, data: data);
final content = contentBuilder(data.valueOrNull ?? [], footer);
return isRefreshable
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content)
: content;
}
}
class PaginationListFooter<T> extends StatelessWidget {
final PaginationController<T> noti;
final AsyncValue<List<T>> data;
final bool isSliver;
const PaginationListFooter({
super.key,
required this.noti,
required this.data,
this.isSliver = false,
});
@override
Widget build(BuildContext context) {
final child = SizedBox(
height: 64,
child: Center(
child:
data.isLoading
? CircularProgressIndicator()
: Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Symbols.close, size: 16),
Text('noFurtherData').tr().fontSize(13),
],
).opacity(0.9),
).padding(all: 8),
);
return VisibilityDetector(
key: Key("pagination-list-${noti.hashCode}"),
onVisibilityChanged: (VisibilityInfo info) {
if (!noti.fetchedAll && !data.isLoading && !data.hasError) {
noti.fetchFurther();
}
},
child: isSliver ? SliverToBoxAdapter(child: child) : child,
);
}
}

View File

@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/chat/chat_room.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/services/file_uploader.dart'; import 'package:island/services/file_uploader.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
@@ -17,7 +18,6 @@ import 'package:island/pods/network.dart';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/screens/chat/chat.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
@@ -664,7 +664,7 @@ class _ChatRoomsList extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final chatRooms = ref.watch(chatroomsJoinedProvider); final chatRooms = ref.watch(chatRoomJoinedNotifierProvider);
return chatRooms.when( return chatRooms.when(
data: (rooms) { data: (rooms) {

View File

@@ -124,7 +124,7 @@ class FileItem extends HookConsumerWidget {
if (confirmed != true) return; if (confirmed != true) return;
} }
await _showEditSheet(context, ref); if (context.mounted) await _showEditSheet(context, ref);
} }
Future<void> _showEditSheet(BuildContext context, WidgetRef ref) async { Future<void> _showEditSheet(BuildContext context, WidgetRef ref) async {

View File

@@ -4,7 +4,7 @@ import 'package:google_fonts/google_fonts.dart';
class InfoRow extends StatelessWidget { class InfoRow extends StatelessWidget {
final String label; final String label;
final String value; final String? value;
final IconData icon; final IconData icon;
final bool monospace; final bool monospace;
final VoidCallback? onTap; final VoidCallback? onTap;
@@ -12,7 +12,7 @@ class InfoRow extends StatelessWidget {
const InfoRow({ const InfoRow({
super.key, super.key,
required this.label, required this.label,
required this.value, this.value,
required this.icon, required this.icon,
this.monospace = false, this.monospace = false,
this.onTap, this.onTap,
@@ -20,8 +20,11 @@ class InfoRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget valueWidget = Text( Widget? valueWidget =
value, value == null
? null
: Text(
value!,
style: style:
monospace monospace
? GoogleFonts.robotoMono(fontSize: 14) ? GoogleFonts.robotoMono(fontSize: 14)
@@ -40,13 +43,16 @@ class InfoRow extends StatelessWidget {
flex: 2, flex: 2,
child: Text( child: Text(
label, label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style:
valueWidget == null
? null
: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
), ),
), ),
const Gap(12), if (valueWidget != null) const Gap(12),
Expanded(flex: 3, child: valueWidget), if (valueWidget != null) Expanded(flex: 3, child: valueWidget),
], ],
); );
} }

View File

@@ -369,8 +369,10 @@ class _EmbeddedPackSwitcherState extends State<_EmbeddedPackSwitcher> {
children: [ children: [
const Gap(12), const Gap(12),
// Vertical, scrollable packs rail like common emoji pickers // Vertical, scrollable packs rail like common emoji pickers
SizedBox( Card(
height: 32, margin: EdgeInsets.zero,
child: SizedBox(
height: 36,
child: ListView.separated( child: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
@@ -380,22 +382,51 @@ class _EmbeddedPackSwitcherState extends State<_EmbeddedPackSwitcher> {
final selected = _index == i; final selected = _index == i;
return Tooltip( return Tooltip(
message: packs[i].name, message: packs[i].name,
child: FilterChip( child: AnimatedContainer(
visualDensity: const VisualDensity( duration: const Duration(milliseconds: 200),
horizontal: 0, curve: Curves.easeInOut,
vertical: -4, decoration: BoxDecoration(
color:
selected
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: const BorderRadius.all(Radius.circular(8)),
border:
selected
? Border.all(
color: Theme.of(context).colorScheme.primary,
width: 4,
)
: null,
), ),
label: Text(packs[i].name, overflow: TextOverflow.ellipsis), margin: const EdgeInsets.only(right: 8),
selected: selected, child: InkWell(
onSelected: (_) { borderRadius: const BorderRadius.all(Radius.circular(8)),
onTap: () {
setState(() => _index = i); setState(() => _index = i);
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
}, },
child: TweenAnimationBuilder<double>(
tween: Tween<double>(end: selected ? 4 : 8),
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
builder: (context, value, _) {
return packs[i].icon != null
? CloudImageWidget(
file: packs[i].icon!,
).clipRRect(all: value)
: CloudImageWidget(
file: packs[i].stickers.firstOrNull?.image,
).clipRRect(all: value);
},
),
),
), ),
); );
}, },
), ),
), ).padding(vertical: 4),
).padding(horizontal: 12),
// Content // Content
Expanded( Expanded(

View File

@@ -1,4 +1,4 @@
platform :osx, '11.0' platform :osx, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -214,25 +214,25 @@ PODS:
- sqflite_darwin (0.0.4): - sqflite_darwin (0.0.4):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (3.50.4): - sqlite3 (3.51.1):
- sqlite3/common (= 3.50.4) - sqlite3/common (= 3.51.1)
- sqlite3/common (3.50.4) - sqlite3/common (3.51.1)
- sqlite3/dbstatvtab (3.50.4): - sqlite3/dbstatvtab (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/fts5 (3.50.4): - sqlite3/fts5 (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/math (3.50.4): - sqlite3/math (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/perf-threadsafe (3.50.4): - sqlite3/perf-threadsafe (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/rtree (3.50.4): - sqlite3/rtree (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3/session (3.50.4): - sqlite3/session (3.51.1):
- sqlite3/common - sqlite3/common
- sqlite3_flutter_libs (0.0.1): - sqlite3_flutter_libs (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (~> 3.50.4) - sqlite3 (~> 3.51.1)
- sqlite3/dbstatvtab - sqlite3/dbstatvtab
- sqlite3/fts5 - sqlite3/fts5
- sqlite3/math - sqlite3/math
@@ -456,8 +456,8 @@ SPEC CHECKSUMS:
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
syncfusion_pdfviewer_macos: b3b110c68039178ca4105dd03ef38761eca3b36b syncfusion_pdfviewer_macos: b3b110c68039178ca4105dd03ef38761eca3b36b
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
@@ -466,6 +466,6 @@ SPEC CHECKSUMS:
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
window_manager: b729e31d38fb04905235df9ea896128991cad99e window_manager: b729e31d38fb04905235df9ea896128991cad99e
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f PODFILE CHECKSUM: 1e95c36afbfd1cb6423ceca4de7a8e1b256fb6ac
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@@ -613,7 +613,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 12.4;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@@ -751,7 +751,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 12.4;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -777,7 +777,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 12.4;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };

View File

@@ -1257,10 +1257,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: google_fonts name: google_fonts
sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c" sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.2" version: "6.3.3"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@@ -1545,10 +1545,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: local_auth_android name: local_auth_android
sha256: "04dd9050b59cb4bcaf051d44eec65865779a9b2f6daccc523f59f96b565a5d54" sha256: "162b8e177fd9978c4620da2a8002a5c6bed4d20f0c6daf5137e72e9a8b767d2e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.4"
local_auth_darwin: local_auth_darwin:
dependency: transitive dependency: transitive
description: description:
@@ -1633,18 +1633,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: material_symbols_icons name: material_symbols_icons
sha256: "9a7de58ffc299c8e362b4e860e36e1d198fa0981a894376fe1b6bfe52773e15b" sha256: "02555a48e1ec02b16e532dfd4ef13c4f6bf7ec7c20230e58e56641a393433dc3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2874.0" version: "4.2892.0"
media_kit: media_kit:
dependency: "direct main" dependency: "direct main"
description: description:
name: media_kit name: media_kit
sha256: dfd5ab85d49a1806b1314a0b81f3d14da48f0db0a657336b2d77c5f17db28944 sha256: "2a207ea7baf1a2ea2ff2016d512e572ca6fc02a937769effb5c27b4d682b4a53"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.2" version: "1.2.3"
media_kit_libs_android_video: media_kit_libs_android_video:
dependency: transitive dependency: transitive
description: description:
@@ -1697,10 +1697,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: media_kit_video name: media_kit_video
sha256: eac9b5f27310afe6def49f9b785cef155f99f1db6079d2b2b127b8bddafb6472 sha256: afaa509e7b7e0bf247557a3a740cde903a52c34ace9810f94500e127bd7b043d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.1"
menu_base: menu_base:
dependency: transitive dependency: transitive
description: description:
@@ -2354,10 +2354,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b" sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.17" version: "2.4.18"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@@ -2543,10 +2543,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqlite3_flutter_libs name: sqlite3_flutter_libs
sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" sha256: "1e800ebe7f85a80a66adacaa6febe4d5f4d8b75f244e9838a27cb2ffc7aec08d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.40" version: "0.5.41"
sqlparser: sqlparser:
dependency: transitive dependency: transitive
description: description:
@@ -2639,18 +2639,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_flutter_core name: syncfusion_flutter_core
sha256: a55762b7d6fdfe588378127b7b186aad418c04a8670c40d3d3438fa09e3d259a sha256: e68a7e214659faf0df483c760d295ab58e376a639285d2a9f7d1e43351efcbb3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
syncfusion_flutter_pdf: syncfusion_flutter_pdf:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_flutter_pdf name: syncfusion_flutter_pdf
sha256: ac85ff223e830f3f9d4f799bc5817e1ce6a136d60b3f8dac28bd9d6860db56e0 sha256: "7a09952cc5193f78211a84eed3f0bd67876861997221b2479889013951fb5fba"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
syncfusion_flutter_pdfviewer: syncfusion_flutter_pdfviewer:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -2663,50 +2663,50 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_flutter_signaturepad name: syncfusion_flutter_signaturepad
sha256: ea5f6d5245f2297d1e6d296bfd43d5bd1ce5a13f8e24b6824fcd58ba9e152f72 sha256: f65d43512fccb2733dcee292a36d561e1963c61f86482007d70c0d4a007718f1
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
syncfusion_pdfviewer_linux: syncfusion_pdfviewer_linux:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_pdfviewer_linux name: syncfusion_pdfviewer_linux
sha256: "9f7c99392acad22d3e837490226ccad85a172a9b0b958a9e6346237cb966c9c8" sha256: "2a80a35c11fd3f06444ce4cad2e7b36ce1cea28030379186a8545851588d1784"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
syncfusion_pdfviewer_macos: syncfusion_pdfviewer_macos:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_pdfviewer_macos name: syncfusion_pdfviewer_macos
sha256: "4965ce7c8ccdd6d9cb1248bd87af4c4f69af7e7bdd822e1f13a94e25dc62262b" sha256: "9bf2c720d59539cf2e621b9b5214301c3ae9d4c62c1a5a2e0e9c2b20f9e4727d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
syncfusion_pdfviewer_platform_interface: syncfusion_pdfviewer_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_pdfviewer_platform_interface name: syncfusion_pdfviewer_platform_interface
sha256: bc1121557352cb1b9710a79963300799dc6ff129522e04a47fcbc7a958e2feb8 sha256: b5eded6e60270422db529549142cc16e927dbb7317afe3559140bf4ec4301ac3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
syncfusion_pdfviewer_web: syncfusion_pdfviewer_web:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_pdfviewer_web name: syncfusion_pdfviewer_web
sha256: e4b12a8d9e7d3e867a5d5c5153e94db028b43b52b7b11be3a1c36f49b87c93db sha256: "251e4d43a54958c17ba33bf27a0ca143224663755cac68fe2fe8fb0cce5bd846"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
syncfusion_pdfviewer_windows: syncfusion_pdfviewer_windows:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_pdfviewer_windows name: syncfusion_pdfviewer_windows
sha256: "057f85e3978ebb2550121c6b2237db63f3b195b17e53806b1745dcc2c743bc7d" sha256: "33014789595c9782337773e2c891e25fc4b22ede60cc5c95be032765c5d43989"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "31.2.15" version: "31.2.16"
synchronized: synchronized:
dependency: transitive dependency: transitive
description: description:
@@ -2727,34 +2727,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker name: talker
sha256: "31b3081b6787c93b41bc14182229681a4a48b8b22f4b368f3493e7f41825a36d" sha256: "5ab800e29d91ce7728fa218c8a7d46f6c228202ac89af650c3f82cb938a64ba6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.1.3"
talker_dio_logger: talker_dio_logger:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker_dio_logger name: talker_dio_logger
sha256: "5534edf33aa913bdc9b4c733baee4795a884e46e6e632b024762738b53622da5" sha256: "214a31d2ecc488ae6abf623ca9dac3831d34e66195f633bd1909a9d0c282ab8c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.1.3"
talker_flutter: talker_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker_flutter name: talker_flutter
sha256: cd276e639841ddcd50effe86bc16e04ff9a58d1a06d470b0c3c7d62f13579ae8 sha256: "79158cf0fe3fd2bcdb1dc6f5a870cb623f18d0b59a4fd87414f53ce446d80a45"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.1.3"
talker_logger: talker_logger:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker_logger name: talker_logger
sha256: "5e2cc1dd9cbb7811c44ed620b5365768e30b86b6a65e909a5c2324722517f077" sha256: bc75612ace4dbb82fbad36181ff27e95b1ee152c719c2fea6b8ac59c4f091cd2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.1.3"
talker_riverpod_logger: talker_riverpod_logger:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -52,13 +52,13 @@ dependencies:
flutter_highlight: ^0.7.0 flutter_highlight: ^0.7.0
uuid: ^4.5.2 uuid: ^4.5.2
url_launcher: ^6.3.2 url_launcher: ^6.3.2
google_fonts: ^6.3.2 google_fonts: ^6.3.3
gap: ^3.0.1 gap: ^3.0.1
cached_network_image: ^3.4.1 cached_network_image: ^3.4.1
web: ^1.1.1 web: ^1.1.1
flutter_blurhash: ^0.9.1 flutter_blurhash: ^0.9.1
media_kit: ^1.2.2 media_kit: ^1.2.3
media_kit_video: ^2.0.0 media_kit_video: ^2.0.1
media_kit_libs_video: ^1.0.7 media_kit_libs_video: ^1.0.7
flutter_cache_manager: ^3.4.1 flutter_cache_manager: ^3.4.1
@@ -83,7 +83,7 @@ dependencies:
flutter_udid: ^4.1.1 flutter_udid: ^4.1.1
firebase_core: ^4.2.1 firebase_core: ^4.2.1
web_socket_channel: ^3.0.3 web_socket_channel: ^3.0.3
material_symbols_icons: ^4.2874.0 material_symbols_icons: ^4.2892.0
drift: ^2.28.2 drift: ^2.28.2
drift_flutter: ^0.2.7 drift_flutter: ^0.2.7
path: ^1.9.1 path: ^1.9.1
@@ -155,10 +155,10 @@ dependencies:
dart_ipc: ^1.0.1 dart_ipc: ^1.0.1
pretty_diff_text: ^2.1.0 pretty_diff_text: ^2.1.0
window_manager: ^0.5.1 window_manager: ^0.5.1
talker: ^5.1.1 talker: ^5.1.3
talker_flutter: ^5.1.1 talker_flutter: ^5.1.3
talker_logger: ^5.1.1 talker_logger: ^5.1.3
talker_dio_logger: ^5.1.1 talker_dio_logger: ^5.1.3
talker_riverpod_logger: ^5.0.1 talker_riverpod_logger: ^5.0.1
syncfusion_flutter_pdfviewer: ^31.1.21 syncfusion_flutter_pdfviewer: ^31.1.21
swipe_to: ^1.0.6 swipe_to: ^1.0.6