Compare commits
11 Commits
3.5.0+150
...
6aba84e506
| Author | SHA1 | Date | |
|---|---|---|---|
|
6aba84e506
|
|||
|
c6f104afc7
|
|||
|
4181fd0090
|
|||
|
84bca9601a
|
|||
|
31b83b2d27
|
|||
|
dfcb089c69
|
|||
|
fe365e8c6d
|
|||
|
b5262137ad
|
|||
|
11e93314c7
|
|||
|
c8658bc0ca
|
|||
|
b2f689693b
|
@@ -1020,6 +1020,8 @@
|
||||
"uploadFile": "Upload File",
|
||||
"authDeviceChallenges": "Device Usage",
|
||||
"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",
|
||||
"auto": "Auto",
|
||||
"manual": "Manual",
|
||||
@@ -1489,5 +1491,6 @@
|
||||
"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.",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -268,9 +268,9 @@ PODS:
|
||||
- Flutter
|
||||
- record_ios (1.1.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.21.3):
|
||||
- SDWebImage/Core (= 5.21.3)
|
||||
- SDWebImage/Core (5.21.3)
|
||||
- SDWebImage (5.21.5):
|
||||
- SDWebImage/Core (= 5.21.5)
|
||||
- SDWebImage/Core (5.21.5)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -281,25 +281,25 @@ PODS:
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.50.4):
|
||||
- sqlite3/common (= 3.50.4)
|
||||
- sqlite3/common (3.50.4)
|
||||
- sqlite3/dbstatvtab (3.50.4):
|
||||
- sqlite3 (3.51.1):
|
||||
- sqlite3/common (= 3.51.1)
|
||||
- sqlite3/common (3.51.1)
|
||||
- sqlite3/dbstatvtab (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/fts5 (3.50.4):
|
||||
- sqlite3/fts5 (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/math (3.50.4):
|
||||
- sqlite3/math (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.50.4):
|
||||
- sqlite3/perf-threadsafe (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.50.4):
|
||||
- sqlite3/rtree (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/session (3.50.4):
|
||||
- sqlite3/session (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.50.4)
|
||||
- sqlite3 (~> 3.51.1)
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/math
|
||||
@@ -545,13 +545,13 @@ SPEC CHECKSUMS:
|
||||
protocol_handler_ios: 59f23ee71f3ec602d67902ca7f669a80957888d5
|
||||
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
||||
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
|
||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14
|
||||
|
||||
@@ -358,23 +358,29 @@ class AppDatabase extends _$AppDatabase {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> saveChatRooms(List<SnChatRoom> rooms) async {
|
||||
Future<void> saveChatRooms(
|
||||
List<SnChatRoom> rooms, {
|
||||
bool override = false,
|
||||
}) async {
|
||||
await transaction(() async {
|
||||
// 1. Identify rooms to remove
|
||||
final remoteRoomIds = rooms.map((r) => r.id).toSet();
|
||||
final currentRooms = await select(chatRooms).get();
|
||||
final currentRoomIds = currentRooms.map((r) => r.id).toSet();
|
||||
final idsToRemove = currentRoomIds.difference(remoteRoomIds);
|
||||
if (override) {
|
||||
// 1. Identify rooms to remove
|
||||
final remoteRoomIds = rooms.map((r) => r.id).toSet();
|
||||
final currentRooms = await select(chatRooms).get();
|
||||
final currentRoomIds = currentRooms.map((r) => r.id).toSet();
|
||||
final idsToRemove = currentRoomIds.difference(remoteRoomIds);
|
||||
|
||||
if (idsToRemove.isNotEmpty) {
|
||||
final idsList = idsToRemove.toList();
|
||||
// Remove messages
|
||||
await (delete(chatMessages)..where((t) => t.roomId.isIn(idsList))).go();
|
||||
// Remove members
|
||||
await (delete(chatMembers)
|
||||
..where((t) => t.chatRoomId.isIn(idsList))).go();
|
||||
// Remove rooms
|
||||
await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go();
|
||||
if (idsToRemove.isNotEmpty) {
|
||||
final idsList = idsToRemove.toList();
|
||||
// Remove messages
|
||||
await (delete(chatMessages)
|
||||
..where((t) => t.roomId.isIn(idsList))).go();
|
||||
// Remove members
|
||||
await (delete(chatMembers)
|
||||
..where((t) => t.chatRoomId.isIn(idsList))).go();
|
||||
// Remove rooms
|
||||
await (delete(chatRooms)..where((t) => t.id.isIn(idsList))).go();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Upsert remote rooms
|
||||
|
||||
@@ -216,20 +216,20 @@ sealed class SnAuthDevice with _$SnAuthDevice {
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnAuthDeviceWithChallenge with _$SnAuthDeviceWithChallenge {
|
||||
const factory SnAuthDeviceWithChallenge({
|
||||
sealed class SnAuthDeviceWithSession with _$SnAuthDeviceWithSession {
|
||||
const factory SnAuthDeviceWithSession({
|
||||
required String id,
|
||||
required String deviceId,
|
||||
required String deviceName,
|
||||
required String? deviceLabel,
|
||||
required String accountId,
|
||||
required int platform,
|
||||
required List<SnAuthChallenge> challenges,
|
||||
required List<SnAuthSession> sessions,
|
||||
@Default(false) bool isCurrent,
|
||||
}) = _SnAuthDeviceWithChallengee;
|
||||
}) = _SnAuthDeviceWithSessione;
|
||||
|
||||
factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnAuthDeviceWithChallengeFromJson(json);
|
||||
factory SnAuthDeviceWithSession.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnAuthDeviceWithSessionFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -3068,51 +3068,51 @@ as bool,
|
||||
|
||||
}
|
||||
|
||||
SnAuthDeviceWithChallenge _$SnAuthDeviceWithChallengeFromJson(
|
||||
SnAuthDeviceWithSession _$SnAuthDeviceWithSessionFromJson(
|
||||
Map<String, dynamic> json
|
||||
) {
|
||||
return _SnAuthDeviceWithChallengee.fromJson(
|
||||
return _SnAuthDeviceWithSessione.fromJson(
|
||||
json
|
||||
);
|
||||
}
|
||||
|
||||
/// @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;
|
||||
/// Create a copy of SnAuthDeviceWithChallenge
|
||||
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 SnAuthDeviceWithSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@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();
|
||||
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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
|
||||
abstract mixin class $SnAuthDeviceWithChallengeCopyWith<$Res> {
|
||||
factory $SnAuthDeviceWithChallengeCopyWith(SnAuthDeviceWithChallenge value, $Res Function(SnAuthDeviceWithChallenge) _then) = _$SnAuthDeviceWithChallengeCopyWithImpl;
|
||||
abstract mixin class $SnAuthDeviceWithSessionCopyWith<$Res> {
|
||||
factory $SnAuthDeviceWithSessionCopyWith(SnAuthDeviceWithSession value, $Res Function(SnAuthDeviceWithSession) _then) = _$SnAuthDeviceWithSessionCopyWithImpl;
|
||||
@useResult
|
||||
$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
|
||||
class _$SnAuthDeviceWithChallengeCopyWithImpl<$Res>
|
||||
implements $SnAuthDeviceWithChallengeCopyWith<$Res> {
|
||||
_$SnAuthDeviceWithChallengeCopyWithImpl(this._self, this._then);
|
||||
class _$SnAuthDeviceWithSessionCopyWithImpl<$Res>
|
||||
implements $SnAuthDeviceWithSessionCopyWith<$Res> {
|
||||
_$SnAuthDeviceWithSessionCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnAuthDeviceWithChallenge _self;
|
||||
final $Res Function(SnAuthDeviceWithChallenge) _then;
|
||||
final SnAuthDeviceWithSession _self;
|
||||
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.
|
||||
@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(
|
||||
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
|
||||
@@ -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?,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 int,challenges: null == challenges ? _self.challenges : challenges // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAuthChallenge>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
|
||||
as int,sessions: null == sessions ? _self.sessions : sessions // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
@@ -3146,8 +3146,8 @@ as bool,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnAuthDeviceWithChallenge].
|
||||
extension SnAuthDeviceWithChallengePatterns on SnAuthDeviceWithChallenge {
|
||||
/// Adds pattern-matching-related methods to [SnAuthDeviceWithSession].
|
||||
extension SnAuthDeviceWithSessionPatterns on SnAuthDeviceWithSession {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// 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;
|
||||
switch (_that) {
|
||||
case _SnAuthDeviceWithChallengee() when $default != null:
|
||||
case _SnAuthDeviceWithSessione() when $default != null:
|
||||
return $default(_that);case _:
|
||||
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;
|
||||
switch (_that) {
|
||||
case _SnAuthDeviceWithChallengee():
|
||||
case _SnAuthDeviceWithSessione():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// 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;
|
||||
switch (_that) {
|
||||
case _SnAuthDeviceWithChallengee() when $default != null:
|
||||
case _SnAuthDeviceWithSessione() when $default != null:
|
||||
return $default(_that);case _:
|
||||
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) {
|
||||
case _SnAuthDeviceWithChallengee() when $default != null:
|
||||
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);case _:
|
||||
case _SnAuthDeviceWithSessione() when $default != null:
|
||||
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.sessions,_that.isCurrent);case _:
|
||||
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) {
|
||||
case _SnAuthDeviceWithChallengee():
|
||||
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);}
|
||||
case _SnAuthDeviceWithSessione():
|
||||
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`
|
||||
///
|
||||
@@ -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) {
|
||||
case _SnAuthDeviceWithChallengee() when $default != null:
|
||||
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.challenges,_that.isCurrent);case _:
|
||||
case _SnAuthDeviceWithSessione() when $default != null:
|
||||
return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that.accountId,_that.platform,_that.sessions,_that.isCurrent);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -3273,9 +3273,9 @@ return $default(_that.id,_that.deviceId,_that.deviceName,_that.deviceLabel,_that
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnAuthDeviceWithChallengee implements SnAuthDeviceWithChallenge {
|
||||
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;
|
||||
factory _SnAuthDeviceWithChallengee.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceWithChallengeeFromJson(json);
|
||||
class _SnAuthDeviceWithSessione implements SnAuthDeviceWithSession {
|
||||
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 _SnAuthDeviceWithSessione.fromJson(Map<String, dynamic> json) => _$SnAuthDeviceWithSessioneFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String deviceId;
|
||||
@@ -3283,49 +3283,49 @@ class _SnAuthDeviceWithChallengee implements SnAuthDeviceWithChallenge {
|
||||
@override final String? deviceLabel;
|
||||
@override final String accountId;
|
||||
@override final int platform;
|
||||
final List<SnAuthChallenge> _challenges;
|
||||
@override List<SnAuthChallenge> get challenges {
|
||||
if (_challenges is EqualUnmodifiableListView) return _challenges;
|
||||
final List<SnAuthSession> _sessions;
|
||||
@override List<SnAuthSession> get sessions {
|
||||
if (_sessions is EqualUnmodifiableListView) return _sessions;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_challenges);
|
||||
return EqualUnmodifiableListView(_sessions);
|
||||
}
|
||||
|
||||
@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.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnAuthDeviceWithChallengeeCopyWith<_SnAuthDeviceWithChallengee> get copyWith => __$SnAuthDeviceWithChallengeeCopyWithImpl<_SnAuthDeviceWithChallengee>(this, _$identity);
|
||||
_$SnAuthDeviceWithSessioneCopyWith<_SnAuthDeviceWithSessione> get copyWith => __$SnAuthDeviceWithSessioneCopyWithImpl<_SnAuthDeviceWithSessione>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnAuthDeviceWithChallengeeToJson(this, );
|
||||
return _$SnAuthDeviceWithSessioneToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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
|
||||
abstract mixin class _$SnAuthDeviceWithChallengeeCopyWith<$Res> implements $SnAuthDeviceWithChallengeCopyWith<$Res> {
|
||||
factory _$SnAuthDeviceWithChallengeeCopyWith(_SnAuthDeviceWithChallengee value, $Res Function(_SnAuthDeviceWithChallengee) _then) = __$SnAuthDeviceWithChallengeeCopyWithImpl;
|
||||
abstract mixin class _$SnAuthDeviceWithSessioneCopyWith<$Res> implements $SnAuthDeviceWithSessionCopyWith<$Res> {
|
||||
factory _$SnAuthDeviceWithSessioneCopyWith(_SnAuthDeviceWithSessione value, $Res Function(_SnAuthDeviceWithSessione) _then) = __$SnAuthDeviceWithSessioneCopyWithImpl;
|
||||
@override @useResult
|
||||
$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
|
||||
class __$SnAuthDeviceWithChallengeeCopyWithImpl<$Res>
|
||||
implements _$SnAuthDeviceWithChallengeeCopyWith<$Res> {
|
||||
__$SnAuthDeviceWithChallengeeCopyWithImpl(this._self, this._then);
|
||||
class __$SnAuthDeviceWithSessioneCopyWithImpl<$Res>
|
||||
implements _$SnAuthDeviceWithSessioneCopyWith<$Res> {
|
||||
__$SnAuthDeviceWithSessioneCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnAuthDeviceWithChallengee _self;
|
||||
final $Res Function(_SnAuthDeviceWithChallengee) _then;
|
||||
final _SnAuthDeviceWithSessione _self;
|
||||
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.
|
||||
@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,}) {
|
||||
return _then(_SnAuthDeviceWithChallengee(
|
||||
@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(_SnAuthDeviceWithSessione(
|
||||
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,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?,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 int,challenges: null == challenges ? _self._challenges : challenges // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAuthChallenge>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
|
||||
as int,sessions: null == sessions ? _self._sessions : sessions // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnAuthSession>,isCurrent: null == isCurrent ? _self.isCurrent : isCurrent // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -367,24 +367,24 @@ Map<String, dynamic> _$SnAuthDeviceToJson(_SnAuthDevice instance) =>
|
||||
'is_current': instance.isCurrent,
|
||||
};
|
||||
|
||||
_SnAuthDeviceWithChallengee _$SnAuthDeviceWithChallengeeFromJson(
|
||||
_SnAuthDeviceWithSessione _$SnAuthDeviceWithSessioneFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _SnAuthDeviceWithChallengee(
|
||||
) => _SnAuthDeviceWithSessione(
|
||||
id: json['id'] as String,
|
||||
deviceId: json['device_id'] as String,
|
||||
deviceName: json['device_name'] as String,
|
||||
deviceLabel: json['device_label'] as String?,
|
||||
accountId: json['account_id'] as String,
|
||||
platform: (json['platform'] as num).toInt(),
|
||||
challenges:
|
||||
(json['challenges'] as List<dynamic>)
|
||||
.map((e) => SnAuthChallenge.fromJson(e as Map<String, dynamic>))
|
||||
sessions:
|
||||
(json['sessions'] as List<dynamic>)
|
||||
.map((e) => SnAuthSession.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
isCurrent: json['is_current'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson(
|
||||
_SnAuthDeviceWithChallengee instance,
|
||||
Map<String, dynamic> _$SnAuthDeviceWithSessioneToJson(
|
||||
_SnAuthDeviceWithSessione instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'device_id': instance.deviceId,
|
||||
@@ -392,7 +392,7 @@ Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson(
|
||||
'device_label': instance.deviceLabel,
|
||||
'account_id': instance.accountId,
|
||||
'platform': instance.platform,
|
||||
'challenges': instance.challenges.map((e) => e.toJson()).toList(),
|
||||
'sessions': instance.sessions.map((e) => e.toJson()).toList(),
|
||||
'is_current': instance.isCurrent,
|
||||
};
|
||||
|
||||
|
||||
@@ -59,8 +59,9 @@ sealed class SnAuthSession with _$SnAuthSession {
|
||||
required DateTime? expiredAt,
|
||||
required List<dynamic> audiences,
|
||||
required List<dynamic> scopes,
|
||||
required String ipAddress,
|
||||
required String userAgent,
|
||||
required String? ipAddress,
|
||||
required String? userAgent,
|
||||
required GeoIpLocation? location,
|
||||
required int type,
|
||||
required String accountId,
|
||||
required DateTime createdAt,
|
||||
|
||||
@@ -885,7 +885,7 @@ $GeoIpLocationCopyWith<$Res>? get location {
|
||||
/// @nodoc
|
||||
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
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -898,16 +898,16 @@ $SnAuthSessionCopyWith<SnAuthSession> get copyWith => _$SnAuthSessionCopyWithImp
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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;
|
||||
@useResult
|
||||
$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
|
||||
@@ -935,7 +935,7 @@ class _$SnAuthSessionCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnAuthSession
|
||||
/// 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(
|
||||
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
|
||||
@@ -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?,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>,ipAddress: null == 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,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,ipAddress: freezed == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
|
||||
as String?,userAgent: freezed == userAgent ? _self.userAgent : userAgent // 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 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
|
||||
@@ -953,7 +954,19 @@ as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ign
|
||||
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) {
|
||||
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();
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
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`
|
||||
///
|
||||
@@ -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) {
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -1085,7 +1098,7 @@ return $default(_that.id,_that.label,_that.lastGrantedAt,_that.expiredAt,_that.a
|
||||
@JsonSerializable()
|
||||
|
||||
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);
|
||||
|
||||
@override final String id;
|
||||
@@ -1106,8 +1119,9 @@ class _SnAuthSession implements SnAuthSession {
|
||||
return EqualUnmodifiableListView(_scopes);
|
||||
}
|
||||
|
||||
@override final String ipAddress;
|
||||
@override final String userAgent;
|
||||
@override final String? ipAddress;
|
||||
@override final String? userAgent;
|
||||
@override final GeoIpLocation? location;
|
||||
@override final int type;
|
||||
@override final String accountId;
|
||||
@override final DateTime createdAt;
|
||||
@@ -1127,16 +1141,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
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)
|
||||
@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
|
||||
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;
|
||||
@override @useResult
|
||||
$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
|
||||
@@ -1164,7 +1178,7 @@ class __$SnAuthSessionCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnAuthSession
|
||||
/// 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(
|
||||
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
|
||||
@@ -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?,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>,ipAddress: null == 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,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,ipAddress: freezed == ipAddress ? _self.ipAddress : ipAddress // ignore: cast_nullable_to_non_nullable
|
||||
as String?,userAgent: freezed == userAgent ? _self.userAgent : userAgent // 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 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
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -96,8 +96,14 @@ _SnAuthSession _$SnAuthSessionFromJson(Map<String, dynamic> json) =>
|
||||
: DateTime.parse(json['expired_at'] as String),
|
||||
audiences: json['audiences'] as List<dynamic>,
|
||||
scopes: json['scopes'] as List<dynamic>,
|
||||
ipAddress: json['ip_address'] as String,
|
||||
userAgent: json['user_agent'] as String,
|
||||
ipAddress: json['ip_address'] 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(),
|
||||
accountId: json['account_id'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
@@ -118,6 +124,7 @@ Map<String, dynamic> _$SnAuthSessionToJson(_SnAuthSession instance) =>
|
||||
'scopes': instance.scopes,
|
||||
'ip_address': instance.ipAddress,
|
||||
'user_agent': instance.userAgent,
|
||||
'location': instance.location?.toJson(),
|
||||
'type': instance.type,
|
||||
'account_id': instance.accountId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
|
||||
373
lib/pods/chat/chat_room.dart
Normal file
373
lib/pods/chat/chat_room.dart
Normal 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();
|
||||
}
|
||||
355
lib/pods/chat/chat_room.g.dart
Normal file
355
lib/pods/chat/chat_room.g.dart
Normal 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
|
||||
@@ -1,5 +0,0 @@
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
|
||||
final isSyncingProvider = StateProvider.autoDispose<bool>((ref) => false);
|
||||
|
||||
final flashingMessagesProvider = StateProvider<Set<String>>((ref) => {});
|
||||
@@ -3,10 +3,10 @@ import "dart:convert";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.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/chat/messages_notifier.dart";
|
||||
import "package:island/pods/websocket.dart";
|
||||
import "package:island/screens/chat/chat.dart";
|
||||
import "package:island/widgets/chat/call_button.dart";
|
||||
import "package:riverpod_annotation/riverpod_annotation.dart";
|
||||
|
||||
@@ -16,10 +16,9 @@ final currentSubscribedChatIdProvider = StateProvider<String?>((ref) => null);
|
||||
|
||||
@riverpod
|
||||
class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
|
||||
late final String _roomId;
|
||||
late final SnChatRoom _chatRoom;
|
||||
late final SnChatMember _chatIdentity;
|
||||
late final MessagesNotifier _messagesNotifier;
|
||||
late SnChatRoom _chatRoom;
|
||||
late SnChatMember _chatIdentity;
|
||||
late MessagesNotifier _messagesNotifier;
|
||||
|
||||
final List<SnChatMember> _typingStatuses = [];
|
||||
Timer? _typingCleanupTimer;
|
||||
@@ -29,10 +28,11 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
|
||||
|
||||
@override
|
||||
List<SnChatMember> build(String roomId) {
|
||||
_roomId = roomId;
|
||||
final ws = ref.watch(websocketProvider);
|
||||
final chatRoomAsync = ref.watch(chatroomProvider(roomId));
|
||||
final chatIdentityAsync = ref.watch(chatroomIdentityProvider(roomId));
|
||||
final chatRoomAsync = ref.watch(ChatRoomNotifierProvider(roomId));
|
||||
final chatIdentityAsync = ref.watch(
|
||||
ChatRoomIdentityNotifierProvider(roomId),
|
||||
);
|
||||
_messagesNotifier = ref.watch(messagesNotifierProvider(roomId).notifier);
|
||||
|
||||
if (chatRoomAsync.isLoading || chatIdentityAsync.isLoading) {
|
||||
@@ -199,7 +199,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
|
||||
jsonEncode(
|
||||
WebSocketPacket(
|
||||
type: 'messages.read',
|
||||
data: {'chat_room_id': _roomId},
|
||||
data: {'chat_room_id': roomId},
|
||||
endpoint: 'sphere',
|
||||
),
|
||||
),
|
||||
@@ -216,7 +216,7 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
|
||||
jsonEncode(
|
||||
WebSocketPacket(
|
||||
type: 'messages.typing',
|
||||
data: {'chat_room_id': _roomId},
|
||||
data: {'chat_room_id': roomId},
|
||||
endpoint: 'sphere',
|
||||
),
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'chat_subscribe.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatSubscribeNotifierHash() =>
|
||||
r'c605e0c9c45df64e5ba7b65f8de9b47bde8e2b3b';
|
||||
r'beec1ddf2e13f6d5af8a08c2c81eff740ae9b986';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -60,7 +60,7 @@ class ChatUnreadCountNotifier extends _$ChatUnreadCountNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
@Riverpod(keepAlive: true)
|
||||
class ChatSummary extends _$ChatSummary {
|
||||
@override
|
||||
Future<Map<String, SnChatSummary>> build() async {
|
||||
|
||||
@@ -24,22 +24,22 @@ final chatUnreadCountNotifierProvider =
|
||||
);
|
||||
|
||||
typedef _$ChatUnreadCountNotifier = AutoDisposeAsyncNotifier<int>;
|
||||
String _$chatSummaryHash() => r'8479ef53cfb0b698b800d0117d04774b6f78b3cc';
|
||||
String _$chatSummaryHash() => r'78d927d40cded9d7adbc20bd6f457fdf3c852632';
|
||||
|
||||
/// See also [ChatSummary].
|
||||
@ProviderFor(ChatSummary)
|
||||
final chatSummaryProvider = AutoDisposeAsyncNotifierProvider<
|
||||
ChatSummary,
|
||||
Map<String, SnChatSummary>
|
||||
>.internal(
|
||||
ChatSummary.new,
|
||||
name: r'chatSummaryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$chatSummaryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
final chatSummaryProvider =
|
||||
AsyncNotifierProvider<ChatSummary, Map<String, SnChatSummary>>.internal(
|
||||
ChatSummary.new,
|
||||
name: r'chatSummaryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$chatSummaryHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$ChatSummary = AutoDisposeAsyncNotifier<Map<String, SnChatSummary>>;
|
||||
typedef _$ChatSummary = AsyncNotifier<Map<String, SnChatSummary>>;
|
||||
// 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
|
||||
|
||||
@@ -11,6 +11,7 @@ import "package:island/models/chat.dart";
|
||||
import "package:island/models/file.dart";
|
||||
import "package:island/models/poll.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/lifecycle.dart";
|
||||
import "package:island/pods/network.dart";
|
||||
@@ -19,18 +20,16 @@ import "package:island/talker.dart";
|
||||
import "package:island/widgets/alert.dart";
|
||||
import "package:riverpod_annotation/riverpod_annotation.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";
|
||||
|
||||
part 'messages_notifier.g.dart';
|
||||
|
||||
@riverpod
|
||||
class MessagesNotifier extends _$MessagesNotifier {
|
||||
late final Dio _apiClient;
|
||||
late final AppDatabase _database;
|
||||
late final SnChatRoom _room;
|
||||
late final SnChatMember _identity;
|
||||
late Dio _apiClient;
|
||||
late AppDatabase _database;
|
||||
late SnChatRoom _room;
|
||||
late SnChatMember _identity;
|
||||
|
||||
final Map<String, LocalChatMessage> _pendingMessages = {};
|
||||
final Map<String, Map<int, double?>> _fileUploadProgress = {};
|
||||
@@ -39,7 +38,6 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
bool? _withLinks;
|
||||
bool? _withAttachments;
|
||||
|
||||
late final String _roomId;
|
||||
static const int _pageSize = 20;
|
||||
bool _hasMore = true;
|
||||
bool _isSyncing = false;
|
||||
@@ -48,15 +46,16 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
bool _allRemoteMessagesFetched = false;
|
||||
DateTime? _lastPauseTime;
|
||||
|
||||
late final Future<SnAccount?> Function(String) _fetchAccount;
|
||||
late Future<SnAccount?> Function(String) _fetchAccount;
|
||||
|
||||
@override
|
||||
FutureOr<List<LocalChatMessage>> build(String roomId) async {
|
||||
_roomId = roomId;
|
||||
_apiClient = ref.watch(apiClientProvider);
|
||||
_database = ref.watch(databaseProvider);
|
||||
final room = await ref.watch(chatroomProvider(roomId).future);
|
||||
final identity = await ref.watch(chatroomIdentityProvider(roomId).future);
|
||||
final room = await ref.watch(ChatRoomNotifierProvider(roomId).future);
|
||||
final identity = await ref.watch(
|
||||
ChatRoomIdentityNotifierProvider(roomId).future,
|
||||
);
|
||||
|
||||
// Initialize fetch account method for corrupted data recovery
|
||||
_fetchAccount = (String accountId) async {
|
||||
@@ -144,14 +143,14 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
|
||||
if (searchQuery != null && searchQuery.isNotEmpty) {
|
||||
dbMessages = await _database.searchMessages(
|
||||
_roomId,
|
||||
roomId,
|
||||
searchQuery,
|
||||
withAttachments: withAttachments,
|
||||
fetchAccount: _fetchAccount,
|
||||
);
|
||||
} else {
|
||||
final chatMessagesFromDb = await _database.getMessagesForRoom(
|
||||
_roomId,
|
||||
roomId,
|
||||
offset: offset,
|
||||
limit: take,
|
||||
);
|
||||
@@ -194,9 +193,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
|
||||
if (offset == 0) {
|
||||
final pendingForRoom =
|
||||
_pendingMessages.values
|
||||
.where((msg) => msg.roomId == _roomId)
|
||||
.toList();
|
||||
_pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
|
||||
|
||||
final allMessages = [...pendingForRoom, ...uniqueMessages];
|
||||
_sortMessages(allMessages); // Use the helper function
|
||||
@@ -221,7 +218,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}) async {
|
||||
talker.log('Getting all messages for jump from offset $offset, take $take');
|
||||
final chatMessagesFromDb = await _database.getMessagesForRoom(
|
||||
_roomId,
|
||||
roomId,
|
||||
offset: offset,
|
||||
limit: take,
|
||||
);
|
||||
@@ -245,9 +242,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
|
||||
if (offset == 0) {
|
||||
final pendingForRoom =
|
||||
_pendingMessages.values
|
||||
.where((msg) => msg.roomId == _roomId)
|
||||
.toList();
|
||||
_pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
|
||||
|
||||
final allMessages = [...pendingForRoom, ...uniqueMessages];
|
||||
_sortMessages(allMessages);
|
||||
@@ -272,7 +267,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
talker.log('Fetching messages from API, offset $offset, take $take');
|
||||
if (_totalCount == null) {
|
||||
final response = await _apiClient.get(
|
||||
'/sphere/chat/$_roomId/messages',
|
||||
'/sphere/chat/$roomId/messages',
|
||||
queryParameters: {'offset': 0, 'take': 1},
|
||||
);
|
||||
_totalCount = int.parse(response.headers['x-total']?.firstOrNull ?? '0');
|
||||
@@ -284,7 +279,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
|
||||
final response = await _apiClient.get(
|
||||
'/sphere/chat/$_roomId/messages',
|
||||
'/sphere/chat/$roomId/messages',
|
||||
queryParameters: {'offset': offset, 'take': take},
|
||||
);
|
||||
|
||||
@@ -546,7 +541,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
|
||||
final mockMessage = SnChatMessage(
|
||||
id: 'pending_$nonce',
|
||||
chatRoomId: _roomId,
|
||||
chatRoomId: roomId,
|
||||
senderId: _identity.id,
|
||||
content: content,
|
||||
createdAt: DateTime.now(),
|
||||
@@ -590,8 +585,8 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
|
||||
final response = await _apiClient.request(
|
||||
editingTo == null
|
||||
? '/sphere/chat/$_roomId/messages'
|
||||
: '/sphere/chat/$_roomId/messages/${editingTo.id}',
|
||||
? '/sphere/chat/$roomId/messages'
|
||||
: '/sphere/chat/$roomId/messages/${editingTo.id}',
|
||||
data: {
|
||||
'content': content,
|
||||
'attachments_id': cloudAttachments.map((e) => e.id).toList(),
|
||||
@@ -731,7 +726,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
|
||||
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
|
||||
if (_isJumping) {
|
||||
@@ -783,7 +778,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
|
||||
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
|
||||
if (_isJumping) {
|
||||
@@ -883,7 +878,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
|
||||
try {
|
||||
await _apiClient.delete('/sphere/chat/$_roomId/messages/$messageId');
|
||||
await _apiClient.delete('/sphere/chat/$roomId/messages/$messageId');
|
||||
await receiveMessageDeletion(messageId);
|
||||
} catch (err, stackTrace) {
|
||||
talker.log(
|
||||
@@ -991,7 +986,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
}
|
||||
|
||||
final response = await _apiClient.get(
|
||||
'/sphere/chat/$_roomId/messages/$messageId',
|
||||
'/sphere/chat/$roomId/messages/$messageId',
|
||||
);
|
||||
final remoteMessage = SnChatMessage.fromJson(response.data);
|
||||
final message = LocalChatMessage.fromRemoteMessage(
|
||||
@@ -1048,7 +1043,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
final query = _database.customSelect(
|
||||
'SELECT COUNT(*) as count FROM chat_messages WHERE room_id = ? AND created_at > ?',
|
||||
variables: [
|
||||
Variable.withString(_roomId),
|
||||
Variable.withString(roomId),
|
||||
Variable.withDateTime(message.createdAt),
|
||||
],
|
||||
readsFrom: {_database.chatMessages},
|
||||
|
||||
@@ -6,7 +6,7 @@ part of 'messages_notifier.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$messagesNotifierHash() => r'27ce32c54e317a04e1d554ed4a70a24e4503fdd1';
|
||||
String _$messagesNotifierHash() => r'd76d799494b06fac2adc42d94b7ecd7b8d68c352';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
@@ -2,14 +2,24 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/file_list_item.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/paging.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
|
||||
part 'file_list.g.dart';
|
||||
|
||||
@riverpod
|
||||
class CloudFileListNotifier extends _$CloudFileListNotifier
|
||||
with CursorPagingNotifierMixin<FileListItem> {
|
||||
Future<Map<String, dynamic>?> billingUsage(Ref ref) async {
|
||||
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? _poolId;
|
||||
String? _query;
|
||||
@@ -42,12 +52,7 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> fetch({
|
||||
required String? cursor,
|
||||
}) async {
|
||||
Future<List<FileListItem>> fetch() async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
|
||||
final queryParameters = <String, String>{'path': _currentPath};
|
||||
@@ -83,21 +88,16 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
||||
...files.map((file) => FileListItem.file(file)),
|
||||
];
|
||||
|
||||
// The new API returns all files in the path, no pagination
|
||||
return CursorPagingData(items: items, hasMore: false, nextCursor: null);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<Map<String, dynamic>?> billingUsage(Ref ref) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final response = await client.get('/drive/billing/usage');
|
||||
return response.data;
|
||||
}
|
||||
final unindexedFileListNotifierProvider = AsyncNotifierProvider(
|
||||
UnindexedFileListNotifier.new,
|
||||
);
|
||||
|
||||
@riverpod
|
||||
class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
with CursorPagingNotifierMixin<FileListItem> {
|
||||
class UnindexedFileListNotifier extends AsyncNotifier<List<FileListItem>>
|
||||
with AsyncPaginationController<FileListItem> {
|
||||
String? _poolId;
|
||||
bool _recycled = false;
|
||||
String? _query;
|
||||
@@ -129,21 +129,15 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> build() => fetch(cursor: null);
|
||||
static const int pageSize = 20;
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<FileListItem>> fetch({
|
||||
required String? cursor,
|
||||
}) async {
|
||||
Future<List<FileListItem>> fetch() async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
|
||||
final offset = cursor != null ? int.tryParse(cursor) ?? 0 : 0;
|
||||
const take = 50; // Default page size
|
||||
|
||||
final queryParameters = <String, String>{
|
||||
'take': take.toString(),
|
||||
'offset': offset.toString(),
|
||||
'take': pageSize.toString(),
|
||||
'offset': fetchedCount.toString(),
|
||||
};
|
||||
|
||||
if (_poolId != null) {
|
||||
@@ -169,7 +163,7 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
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 =
|
||||
(response.data as List)
|
||||
@@ -179,14 +173,7 @@ class UnindexedFileListNotifier extends _$UnindexedFileListNotifier
|
||||
final List<FileListItem> items =
|
||||
files.map((file) => FileListItem.unindexedFile(file)).toList();
|
||||
|
||||
final hasMore = offset + take < total;
|
||||
final nextCursor = hasMore ? (offset + take).toString() : null;
|
||||
|
||||
return CursorPagingData(
|
||||
items: items,
|
||||
hasMore: hasMore,
|
||||
nextCursor: nextCursor,
|
||||
);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,47 +44,5 @@ final billingQuotaProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
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: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
82
lib/pods/paging.dart
Normal file
82
lib/pods/paging.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
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 = AsyncLoading<List<T>>();
|
||||
|
||||
final newState = await AsyncValue.guard<List<T>>(() async {
|
||||
return await fetch();
|
||||
});
|
||||
state = newState;
|
||||
}
|
||||
|
||||
@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 {
|
||||
// Reset the data
|
||||
totalCount = null;
|
||||
fetchedCount = 0;
|
||||
currentFilter = filter;
|
||||
|
||||
state = AsyncLoading<List<T>>();
|
||||
|
||||
final newState = await AsyncValue.guard<List<T>>(() async {
|
||||
return await fetch();
|
||||
});
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
65
lib/pods/timeline.dart
Normal file
65
lib/pods/timeline.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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:hooks_riverpod/hooks_riverpod.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/network.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:material_symbols_icons/symbols.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
part 'chat.g.dart';
|
||||
import 'package:island/pods/chat/chat_room.dart';
|
||||
|
||||
class ChatRoomListTile extends HookConsumerWidget {
|
||||
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 {
|
||||
final bool isFloating;
|
||||
final TabController tabController;
|
||||
@@ -281,7 +191,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final chats = ref.watch(chatroomsJoinedProvider);
|
||||
final chats = ref.watch(chatRoomJoinedNotifierProvider);
|
||||
|
||||
Widget bodyWidget = Column(
|
||||
children: [
|
||||
@@ -304,7 +214,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
|
||||
(items) => RefreshIndicator(
|
||||
onRefresh:
|
||||
() => Future.sync(() {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
ref.invalidate(chatRoomJoinedNotifierProvider);
|
||||
}),
|
||||
child: SuperListView.builder(
|
||||
padding: EdgeInsets.only(bottom: 96),
|
||||
@@ -354,7 +264,7 @@ class ChatListBodyWidget extends HookConsumerWidget {
|
||||
(error, stack) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
ref.invalidate(chatRoomJoinedNotifierProvider);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -431,7 +341,7 @@ class ChatListScreen extends HookConsumerWidget {
|
||||
|
||||
// Listen for chat rooms refresh events
|
||||
final subscription = eventBus.on<ChatRoomsRefreshEvent>().listen((event) {
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
ref.invalidate(chatRoomJoinedNotifierProvider);
|
||||
});
|
||||
|
||||
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 {
|
||||
const _ChatInvitesSheet();
|
||||
|
||||
@@ -651,7 +521,7 @@ class _ChatInvitesSheet extends HookConsumerWidget {
|
||||
final client = ref.read(apiClientProvider);
|
||||
await client.post('/sphere/chat/invites/${invite.chatRoom!.id}/accept');
|
||||
ref.invalidate(chatroomInvitesProvider);
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
ref.invalidate(chatRoomJoinedNotifierProvider);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -10,8 +10,8 @@ import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/realm.dart';
|
||||
import 'package:island/pods/chat/chat_room.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/chat/chat.dart';
|
||||
import 'package:island/screens/realm/realms.dart';
|
||||
import 'package:island/services/file.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
@@ -47,7 +47,7 @@ class EditChatScreen extends HookConsumerWidget {
|
||||
final isPublic = useState(true);
|
||||
final isCommunity = useState(false);
|
||||
|
||||
final chat = ref.watch(chatroomProvider(id));
|
||||
final chat = ref.watch(ChatRoomNotifierProvider(id));
|
||||
|
||||
final joinedRealms = ref.watch(realmsJoinedProvider);
|
||||
final currentRealm = useState<SnRealm?>(null);
|
||||
|
||||
@@ -3,7 +3,7 @@ import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:gap/gap.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.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:super_sliver_list/super_sliver_list.dart";
|
||||
import "package:easy_localization/easy_localization.dart";
|
||||
@@ -203,7 +203,7 @@ class PublicRoomPreview extends HookConsumerWidget {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post('/sphere/chat/${room.id}/members/me');
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
ref.invalidate(ChatRoomIdentityNotifierProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
|
||||
@@ -14,15 +14,15 @@ import "package:island/models/chat.dart";
|
||||
import "package:island/models/file.dart";
|
||||
import "package:island/models/poll.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/messages_notifier.dart";
|
||||
import "package:island/pods/network.dart";
|
||||
import "package:island/pods/chat/chat_online_count.dart";
|
||||
import "package:island/pods/config.dart";
|
||||
import "package:island/pods/userinfo.dart";
|
||||
import "package:island/screens/chat/search_messages.dart";
|
||||
import "package:island/services/file_uploader.dart";
|
||||
import "package:island/screens/chat/chat.dart";
|
||||
import "package:island/services/responsive.dart";
|
||||
import "package:island/widgets/alert.dart";
|
||||
import "package:island/widgets/app_scaffold.dart";
|
||||
@@ -48,14 +48,12 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final chatRoom = ref.watch(chatroomProvider(id));
|
||||
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
|
||||
final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
|
||||
final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
|
||||
final isSyncing = ref.watch(isSyncingProvider);
|
||||
final onlineCount = ref.watch(chatOnlineCountNotifierProvider(id));
|
||||
final settings = ref.watch(appSettingsNotifierProvider);
|
||||
|
||||
final hasOnlineCount = onlineCount.hasValue;
|
||||
|
||||
if (chatIdentity.isLoading || chatRoom.isLoading) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
@@ -102,7 +100,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
await apiClient.post(
|
||||
'/sphere/chat/${room.id}/members/me',
|
||||
);
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
ref.invalidate(
|
||||
ChatRoomIdentityNotifierProvider(id),
|
||||
);
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
@@ -131,7 +131,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
appBar: AppBar(leading: const PageBackButton()),
|
||||
body: ResponseErrorWidget(
|
||||
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 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(
|
||||
spacing: 4,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Badge(
|
||||
isLabelVisible: hasOnlineCount,
|
||||
label: Text('${(onlineCount as AsyncData?)?.value}'),
|
||||
isLabelVisible: (onlineCount.value ?? 0) > 1,
|
||||
label: Text('${(onlineCount.value ?? 0)}'),
|
||||
textStyle: GoogleFonts.robotoMono(fontSize: 10),
|
||||
textColor: Colors.white,
|
||||
backgroundColor:
|
||||
(onlineCount as AsyncData?)?.value != null &&
|
||||
(onlineCount as AsyncData).value > 1
|
||||
? Colors.green
|
||||
: Colors.grey,
|
||||
(onlineCount.value ?? 0) > 1 ? Colors.green : Colors.grey,
|
||||
offset: Offset(6, 14),
|
||||
child: SizedBox(
|
||||
height: 26,
|
||||
width: 26,
|
||||
@@ -430,9 +438,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
(room!.type == 1 && room.picture?.id == null)
|
||||
? SplitAvatarWidget(
|
||||
filesId:
|
||||
room.members!
|
||||
.map((e) => e.account.profile.picture?.id)
|
||||
.toList(),
|
||||
getValidMembers(
|
||||
room.members!,
|
||||
).map((e) => e.account.profile.picture?.id).toList(),
|
||||
)
|
||||
: room.picture?.id != null
|
||||
? ProfilePictureWidget(
|
||||
@@ -449,7 +457,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
),
|
||||
Text(
|
||||
(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!,
|
||||
).fontSize(15),
|
||||
],
|
||||
@@ -462,7 +472,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
children: [
|
||||
Badge(
|
||||
isLabelVisible: (onlineCount.value ?? 0) > 1,
|
||||
label: Text('${(onlineCount as AsyncData?)?.value}'),
|
||||
label: Text('${(onlineCount.value ?? 0)}'),
|
||||
textStyle: GoogleFonts.robotoMono(fontSize: 10),
|
||||
backgroundColor:
|
||||
(onlineCount.value ?? 0) > 1 ? Colors.green : Colors.grey,
|
||||
@@ -475,9 +485,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
(room!.type == 1 && room.picture?.id == null)
|
||||
? SplitAvatarWidget(
|
||||
filesId:
|
||||
room.members!
|
||||
.map((e) => e.account.profile.picture?.id)
|
||||
.toList(),
|
||||
getValidMembers(
|
||||
room.members!,
|
||||
).map((e) => e.account.profile.picture?.id).toList(),
|
||||
)
|
||||
: room.picture?.id != null
|
||||
? ProfilePictureWidget(
|
||||
@@ -494,7 +504,9 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
),
|
||||
Text(
|
||||
(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!,
|
||||
).fontSize(19),
|
||||
],
|
||||
@@ -734,7 +746,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
appBar: AppBar(
|
||||
leading: !compactHeader ? const Center(child: PageBackButton()) : null,
|
||||
automaticallyImplyLeading: false,
|
||||
toolbarHeight: compactHeader ? null : 80,
|
||||
toolbarHeight: compactHeader ? null : 74,
|
||||
title: chatRoom.when(
|
||||
data:
|
||||
(room) =>
|
||||
|
||||
@@ -7,8 +7,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/pods/chat/chat_room.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_picker.dart';
|
||||
import 'package:island/widgets/account/status.dart';
|
||||
@@ -39,8 +39,8 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final roomState = ref.watch(chatroomProvider(id));
|
||||
final roomIdentity = ref.watch(chatroomIdentityProvider(id));
|
||||
final roomState = ref.watch(ChatRoomNotifierProvider(id));
|
||||
final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
|
||||
final totalMessages = ref.watch(totalMessagesCountProvider(id));
|
||||
|
||||
const kNotifyLevelText = [
|
||||
@@ -56,7 +56,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
'/sphere/chat/$id/members/me/notify',
|
||||
data: {'notify_level': level},
|
||||
);
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
ref.invalidate(ChatRoomIdentityNotifierProvider(id));
|
||||
if (context.mounted) {
|
||||
showSnackBar(
|
||||
'chatNotifyLevelUpdated'.tr(args: [kNotifyLevelText[level].tr()]),
|
||||
@@ -74,7 +74,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
||||
'/sphere/chat/$id/members/me/notify',
|
||||
data: {'break_until': until.toUtc().toIso8601String()},
|
||||
);
|
||||
ref.invalidate(chatroomProvider(id));
|
||||
ref.invalidate(ChatRoomNotifierProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
}
|
||||
@@ -439,8 +439,8 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
|
||||
final chatRoom = ref.watch(chatroomProvider(id));
|
||||
final chatIdentity = ref.watch(ChatRoomIdentityNotifierProvider(id));
|
||||
final chatRoom = ref.watch(ChatRoomNotifierProvider(id));
|
||||
|
||||
final isManagable =
|
||||
chatIdentity.value?.accountId == chatRoom.value?.accountId ||
|
||||
@@ -461,7 +461,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
// 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) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.delete('/sphere/chat/$id');
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
ref.invalidate(chatRoomJoinedNotifierProvider);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
@@ -530,7 +530,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.delete('/sphere/chat/$id/members/me');
|
||||
ref.invalidate(chatroomsJoinedProvider);
|
||||
ref.invalidate(chatRoomJoinedNotifierProvider);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
@@ -648,8 +648,8 @@ class _ChatMemberListSheet extends HookConsumerWidget {
|
||||
final memberState = ref.watch(chatMemberStateProvider(roomId));
|
||||
final memberNotifier = ref.read(chatMemberStateProvider(roomId).notifier);
|
||||
|
||||
final roomIdentity = ref.watch(chatroomIdentityProvider(roomId));
|
||||
final chatRoom = ref.watch(chatroomProvider(roomId));
|
||||
final roomIdentity = ref.watch(ChatRoomIdentityNotifierProvider(roomId));
|
||||
final chatRoom = ref.watch(ChatRoomNotifierProvider(roomId));
|
||||
|
||||
final isManagable =
|
||||
chatRoom.value?.accountId == roomIdentity.value?.accountId ||
|
||||
|
||||
@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.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/chat_rooms.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/chat/message_list_tile.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
@@ -12,6 +11,7 @@ import 'package:island/models/publisher.dart';
|
||||
import 'package:island/models/realm.dart';
|
||||
import 'package:island/models/webfeed.dart';
|
||||
import 'package:island/pods/event_calendar.dart';
|
||||
import 'package:island/pods/timeline.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/screens/auth/login_modal.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/models/post.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/paging/pagination_list.dart';
|
||||
import 'package:island/widgets/post/post_featured.dart';
|
||||
import 'package:island/widgets/post/post_item.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/publisher/publisher_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/widgets/share/share_sheet.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
|
||||
part 'explore.g.dart';
|
||||
|
||||
Widget notificationIndicatorWidget(
|
||||
BuildContext context, {
|
||||
required int count,
|
||||
@@ -114,13 +110,19 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
return () => tabController.removeListener(listener);
|
||||
}, [tabController]);
|
||||
|
||||
final notifier = ref.watch(activityListNotifierProvider.notifier);
|
||||
|
||||
useEffect(() {
|
||||
Future(() {
|
||||
notifier.applyFilter(currentFilter.value);
|
||||
});
|
||||
return null;
|
||||
}, [currentFilter.value]);
|
||||
|
||||
// Listen for post creation events to refresh activities
|
||||
useEffect(() {
|
||||
final subscription = eventBus.on<PostCreatedEvent>().listen((event) {
|
||||
// Refresh all activity lists when a new post is created
|
||||
ref.invalidate(activityListNotifierProvider(null));
|
||||
ref.invalidate(activityListNotifierProvider('subscriptions'));
|
||||
ref.invalidate(activityListNotifierProvider('friends'));
|
||||
ref.invalidate(activityListNotifierProvider);
|
||||
});
|
||||
return subscription.cancel;
|
||||
}, []);
|
||||
@@ -276,7 +278,6 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
query,
|
||||
events,
|
||||
selectedDay,
|
||||
currentFilter.value,
|
||||
)
|
||||
: _buildNarrowBody(context, ref, currentFilter.value),
|
||||
),
|
||||
@@ -315,29 +316,15 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivityList(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
String? filter,
|
||||
) {
|
||||
final activitiesNotifier = ref.watch(
|
||||
activityListNotifierProvider(filter).notifier,
|
||||
);
|
||||
|
||||
Widget _buildActivityList(BuildContext context, WidgetRef ref) {
|
||||
final isWide = isWideScreen(context);
|
||||
|
||||
return PagingHelperSliverView(
|
||||
provider: activityListNotifierProvider(filter),
|
||||
futureRefreshable: activityListNotifierProvider(filter).future,
|
||||
notifierRefreshable: activityListNotifierProvider(filter).notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) => _ActivityListView(
|
||||
data: data,
|
||||
widgetCount: widgetCount,
|
||||
endItemView: endItemView,
|
||||
activitiesNotifier: activitiesNotifier,
|
||||
isWide: isWide,
|
||||
),
|
||||
return PaginationWidget(
|
||||
provider: activityListNotifierProvider,
|
||||
notifier: activityListNotifierProvider.notifier,
|
||||
// Sliver list cannot provide refresh handled by the pagination list
|
||||
isRefreshable: false,
|
||||
contentBuilder: (data) => _ActivityListView(data: data, isWide: isWide),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -350,13 +337,10 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
ValueNotifier<EventCalendarQuery> query,
|
||||
AsyncValue<List<dynamic>> events,
|
||||
ValueNotifier<DateTime> selectedDay,
|
||||
String? currentFilter,
|
||||
) {
|
||||
final bodyView = _buildActivityList(context, ref, currentFilter);
|
||||
final bodyView = _buildActivityList(context, ref);
|
||||
|
||||
final activitiesNotifier = ref.watch(
|
||||
activityListNotifierProvider(currentFilter).notifier,
|
||||
);
|
||||
final notifier = ref.watch(activityListNotifierProvider.notifier);
|
||||
|
||||
return Row(
|
||||
spacing: 12,
|
||||
@@ -364,7 +348,7 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: ExtendedRefreshIndicator(
|
||||
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
|
||||
onRefresh: notifier.refresh,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
const SliverGap(12),
|
||||
@@ -575,17 +559,15 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
notificationUnreadCountNotifierProvider,
|
||||
);
|
||||
|
||||
final activitiesNotifier = ref.watch(
|
||||
activityListNotifierProvider(currentFilter).notifier,
|
||||
);
|
||||
final bodyView = _buildActivityList(context, ref);
|
||||
|
||||
final bodyView = _buildActivityList(context, ref, currentFilter);
|
||||
final notifier = ref.watch(activityListNotifierProvider.notifier);
|
||||
|
||||
return Expanded(
|
||||
child: ExtendedRefreshIndicator(
|
||||
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: ExtendedRefreshIndicator(
|
||||
onRefresh: notifier.refresh,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
const SliverGap(8),
|
||||
@@ -623,8 +605,8 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
bodyView,
|
||||
],
|
||||
),
|
||||
).padding(horizontal: 8),
|
||||
),
|
||||
),
|
||||
).padding(horizontal: 8),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -741,31 +723,20 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _ActivityListView extends HookConsumerWidget {
|
||||
final CursorPagingData<SnTimelineEvent> data;
|
||||
final int widgetCount;
|
||||
final Widget endItemView;
|
||||
final ActivityListNotifier activitiesNotifier;
|
||||
final List<SnTimelineEvent> data;
|
||||
final bool isWide;
|
||||
|
||||
const _ActivityListView({
|
||||
required this.data,
|
||||
required this.widgetCount,
|
||||
required this.endItemView,
|
||||
required this.activitiesNotifier,
|
||||
required this.isWide,
|
||||
});
|
||||
const _ActivityListView({required this.data, required this.isWide});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final notifier = ref.watch(activityListNotifierProvider.notifier);
|
||||
|
||||
return SliverList.separated(
|
||||
itemCount: widgetCount,
|
||||
itemCount: data.length,
|
||||
separatorBuilder: (_, _) => const Gap(8),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
|
||||
final item = data.items[index];
|
||||
final item = data[index];
|
||||
if (item.data == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -778,13 +749,10 @@ class _ActivityListView extends HookConsumerWidget {
|
||||
borderRadius: 8,
|
||||
item: SnPost.fromJson(item.data!),
|
||||
onRefresh: () {
|
||||
activitiesNotifier.forceRefresh();
|
||||
notifier.refresh();
|
||||
},
|
||||
onUpdate: (post) {
|
||||
activitiesNotifier.updateOne(
|
||||
index,
|
||||
item.copyWith(data: post.toJson()),
|
||||
);
|
||||
notifier.updateOne(index, item.copyWith(data: post.toJson()));
|
||||
},
|
||||
);
|
||||
itemWidget = Card(margin: EdgeInsets.zero, child: itemWidget);
|
||||
@@ -801,69 +769,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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -116,7 +116,7 @@ class FileListScreen extends HookConsumerWidget {
|
||||
completer.future
|
||||
.then((uploadedFile) {
|
||||
if (uploadedFile != null) {
|
||||
ref.invalidate(cloudFileListNotifierProvider);
|
||||
ref.invalidate(indexedCloudFileListNotifierProvider);
|
||||
}
|
||||
})
|
||||
.catchError((error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/account.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/content/sheet.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:island/widgets/sites/info_row.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.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';
|
||||
|
||||
@riverpod
|
||||
Future<List<SnAuthDeviceWithChallenge>> authDevices(Ref ref) async {
|
||||
Future<List<SnAuthDeviceWithSession>> authDevices(Ref ref) async {
|
||||
final resp = await ref
|
||||
.watch(apiClientProvider)
|
||||
.get('/pass/accounts/me/devices');
|
||||
final currentId = await getUdid();
|
||||
final data =
|
||||
resp.data.map<SnAuthDeviceWithChallenge>((e) {
|
||||
final ele = SnAuthDeviceWithChallenge.fromJson(e);
|
||||
resp.data.map<SnAuthDeviceWithSession>((e) {
|
||||
final ele = SnAuthDeviceWithSession.fromJson(e);
|
||||
return ele.copyWith(isCurrent: ele.deviceId == currentId);
|
||||
}).toList();
|
||||
return data;
|
||||
}
|
||||
|
||||
class _DeviceListTile extends StatelessWidget {
|
||||
final SnAuthDeviceWithChallenge device;
|
||||
final SnAuthDeviceWithSession device;
|
||||
final Function(String) updateDeviceLabel;
|
||||
final Function(String) logoutDevice;
|
||||
final Function(String) logoutSession;
|
||||
|
||||
const _DeviceListTile({
|
||||
required this.device,
|
||||
required this.updateDeviceLabel,
|
||||
required this.logoutDevice,
|
||||
required this.logoutSession,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -69,11 +73,12 @@ class _DeviceListTile extends StatelessWidget {
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'lastActiveAt'.tr(
|
||||
args: [device.challenges.first.createdAt.formatSystem()],
|
||||
if (device.sessions.isNotEmpty)
|
||||
Text(
|
||||
'lastActiveAt'.tr(
|
||||
args: [device.sessions.first.createdAt.formatSystem()],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: Icon(switch (device.platform) {
|
||||
@@ -114,26 +119,55 @@ class _DeviceListTile extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text('authDeviceChallenges'.tr()),
|
||||
),
|
||||
for (final challenge in device.challenges)
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
title: Text(DateFormat().format(challenge.createdAt.toLocal())),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(challenge.ipAddress),
|
||||
if (challenge.location != null)
|
||||
Row(
|
||||
spacing: 4,
|
||||
children:
|
||||
[challenge.location?.city, challenge.location?.country]
|
||||
.where((e) => e?.isNotEmpty ?? false)
|
||||
.map((e) => Text(e!))
|
||||
.toList(),
|
||||
...device.sessions
|
||||
.map(
|
||||
(session) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 4,
|
||||
children: [
|
||||
InfoRow(
|
||||
label: 'createdAt'.tr(
|
||||
args: [session.createdAt.toLocal().formatSystem()],
|
||||
),
|
||||
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 {
|
||||
final controller = TextEditingController();
|
||||
final label = await showDialog<String>(
|
||||
@@ -249,6 +299,7 @@ class AccountSessionSheet extends HookConsumerWidget {
|
||||
device: device,
|
||||
updateDeviceLabel: updateDeviceLabel,
|
||||
logoutDevice: logoutDevice,
|
||||
logoutSession: logoutSession,
|
||||
);
|
||||
} else {
|
||||
return Dismissible(
|
||||
@@ -304,6 +355,7 @@ class AccountSessionSheet extends HookConsumerWidget {
|
||||
device: device,
|
||||
updateDeviceLabel: updateDeviceLabel,
|
||||
logoutDevice: logoutDevice,
|
||||
logoutSession: logoutSession,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ part of 'account_devices.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$authDevicesHash() => r'35735af4ed75b73fe80c8942e53b3bc26a569c01';
|
||||
String _$authDevicesHash() => r'1af378149286020ec263be178c573ccc247a0cd1';
|
||||
|
||||
/// See also [authDevices].
|
||||
@ProviderFor(authDevices)
|
||||
final authDevicesProvider =
|
||||
AutoDisposeFutureProvider<List<SnAuthDeviceWithChallenge>>.internal(
|
||||
AutoDisposeFutureProvider<List<SnAuthDeviceWithSession>>.internal(
|
||||
authDevices,
|
||||
name: r'authDevicesProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@@ -25,6 +25,6 @@ final authDevicesProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef AuthDevicesRef =
|
||||
AutoDisposeFutureProviderRef<List<SnAuthDeviceWithChallenge>>;
|
||||
AutoDisposeFutureProviderRef<List<SnAuthDeviceWithSession>>;
|
||||
// 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
|
||||
|
||||
@@ -17,6 +17,7 @@ import "package:island/models/wallet.dart";
|
||||
import "package:island/models/realm.dart";
|
||||
import "package:island/models/sticker.dart";
|
||||
import "package:island/pods/config.dart";
|
||||
import "package:island/pods/userinfo.dart";
|
||||
import "package:island/services/autocomplete_service.dart";
|
||||
import "package:island/services/responsive.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;
|
||||
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(
|
||||
margin: EdgeInsets.only(
|
||||
left: leftMargin,
|
||||
@@ -878,9 +887,9 @@ class ChatInput extends HookConsumerWidget {
|
||||
(chatRoom.type == 1 && chatRoom.name == null)
|
||||
? 'chatDirectMessageHint'.tr(
|
||||
args: [
|
||||
chatRoom.members!
|
||||
.map((e) => e.account.nick)
|
||||
.join(', '),
|
||||
getValidMembers(
|
||||
chatRoom.members!,
|
||||
).map((e) => e.account.nick).join(', '),
|
||||
],
|
||||
)
|
||||
: 'chatMessageHint'.tr(
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/translate.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
|
||||
@@ -6,6 +6,7 @@ import "package:gap/gap.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:island/database/message.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/network.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:material_symbols_icons/symbols.dart";
|
||||
|
||||
import "package:island/screens/chat/chat.dart";
|
||||
|
||||
class PublicRoomPreview extends HookConsumerWidget {
|
||||
final String id;
|
||||
final SnChatRoom room;
|
||||
@@ -205,7 +204,7 @@ class PublicRoomPreview extends HookConsumerWidget {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.read(apiClientProvider);
|
||||
await apiClient.post('/sphere/chat/${room.id}/members/me');
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
ref.invalidate(ChatRoomIdentityNotifierProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.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/widgets/alert.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:markdown/markdown.dart' as markdown;
|
||||
import 'package:markdown_widget/markdown_widget.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'image.dart';
|
||||
|
||||
@@ -185,19 +188,33 @@ class MarkdownTextContent extends HookConsumerWidget {
|
||||
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)),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: CloudFileWidget(
|
||||
item: file,
|
||||
heroTag: heroTag,
|
||||
fit: BoxFit.cover,
|
||||
).clipRRect(all: 8),
|
||||
),
|
||||
child: CloudFileWidget(
|
||||
item: file,
|
||||
fit: BoxFit.cover,
|
||||
).clipRRect(all: 8),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ import 'package:island/utils/format.dart';
|
||||
import 'package:island/utils/text.dart';
|
||||
import 'package:island/widgets/alert.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:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
enum FileListMode { normal, unindexed }
|
||||
@@ -59,7 +59,9 @@ class FileListView extends HookConsumerWidget {
|
||||
|
||||
useEffect(() {
|
||||
if (mode.value == FileListMode.normal) {
|
||||
final notifier = ref.read(cloudFileListNotifierProvider.notifier);
|
||||
final notifier = ref.read(
|
||||
indexedCloudFileListNotifierProvider.notifier,
|
||||
);
|
||||
notifier.setPath(currentPath.value);
|
||||
}
|
||||
return null;
|
||||
@@ -70,7 +72,9 @@ class FileListView extends HookConsumerWidget {
|
||||
final unindexedNotifier = ref.read(
|
||||
unindexedFileListNotifierProvider.notifier,
|
||||
);
|
||||
final cloudNotifier = ref.read(cloudFileListNotifierProvider.notifier);
|
||||
final cloudNotifier = ref.read(
|
||||
indexedCloudFileListNotifierProvider.notifier,
|
||||
);
|
||||
final recycled = useState<bool>(false);
|
||||
final poolsAsync = ref.watch(poolsProvider);
|
||||
final isSelectionMode = useState<bool>(false);
|
||||
@@ -115,27 +119,26 @@ class FileListView extends HookConsumerWidget {
|
||||
|
||||
final isRefreshing = ref.watch(
|
||||
mode.value == FileListMode.normal
|
||||
? cloudFileListNotifierProvider.select((value) => value.isLoading)
|
||||
? indexedCloudFileListNotifierProvider.select(
|
||||
(value) => value.isLoading,
|
||||
)
|
||||
: unindexedFileListNotifierProvider.select(
|
||||
(value) => value.isLoading,
|
||||
),
|
||||
);
|
||||
|
||||
final bodyWidget = switch (mode.value) {
|
||||
FileListMode.unindexed => PagingHelperSliverView(
|
||||
FileListMode.unindexed => PaginationWidget(
|
||||
provider: unindexedFileListNotifierProvider,
|
||||
futureRefreshable: unindexedFileListNotifierProvider.future,
|
||||
notifierRefreshable: unindexedFileListNotifierProvider.notifier,
|
||||
notifier: unindexedFileListNotifierProvider.notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) =>
|
||||
data.items.isEmpty
|
||||
(data) =>
|
||||
data.isEmpty
|
||||
? SliverToBoxAdapter(
|
||||
child: _buildEmptyUnindexedFilesHint(ref),
|
||||
)
|
||||
: _buildUnindexedFileListContent(
|
||||
data.items,
|
||||
widgetCount,
|
||||
endItemView,
|
||||
data,
|
||||
ref,
|
||||
context,
|
||||
viewMode,
|
||||
@@ -144,20 +147,17 @@ class FileListView extends HookConsumerWidget {
|
||||
currentVisibleItems,
|
||||
),
|
||||
),
|
||||
_ => PagingHelperSliverView(
|
||||
provider: cloudFileListNotifierProvider,
|
||||
futureRefreshable: cloudFileListNotifierProvider.future,
|
||||
notifierRefreshable: cloudFileListNotifierProvider.notifier,
|
||||
_ => PaginationWidget(
|
||||
provider: indexedCloudFileListNotifierProvider,
|
||||
notifier: indexedCloudFileListNotifierProvider.notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) =>
|
||||
data.items.isEmpty
|
||||
(data) =>
|
||||
data.isEmpty
|
||||
? SliverToBoxAdapter(
|
||||
child: _buildEmptyDirectoryHint(ref, currentPath),
|
||||
)
|
||||
: _buildFileListContent(
|
||||
data.items,
|
||||
widgetCount,
|
||||
endItemView,
|
||||
data,
|
||||
ref,
|
||||
context,
|
||||
currentPath,
|
||||
@@ -255,7 +255,7 @@ class FileListView extends HookConsumerWidget {
|
||||
completer.future
|
||||
.then((uploadedFile) {
|
||||
if (uploadedFile != null) {
|
||||
ref.invalidate(cloudFileListNotifierProvider);
|
||||
ref.invalidate(indexedCloudFileListNotifierProvider);
|
||||
}
|
||||
})
|
||||
.catchError((error) {
|
||||
@@ -532,7 +532,7 @@ class FileListView extends HookConsumerWidget {
|
||||
isSelectionMode.value = false;
|
||||
ref.invalidate(
|
||||
mode.value == FileListMode.normal
|
||||
? cloudFileListNotifierProvider
|
||||
? indexedCloudFileListNotifierProvider
|
||||
: unindexedFileListNotifierProvider,
|
||||
);
|
||||
showSnackBar('Deleted $count files.');
|
||||
@@ -560,8 +560,6 @@ class FileListView extends HookConsumerWidget {
|
||||
|
||||
Widget _buildFileListContent(
|
||||
List<FileListItem> items,
|
||||
int widgetCount,
|
||||
Widget endItemView,
|
||||
WidgetRef ref,
|
||||
BuildContext context,
|
||||
ValueNotifier<String> currentPath,
|
||||
@@ -580,10 +578,6 @@ class FileListView extends HookConsumerWidget {
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
|
||||
if (index >= items.length) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -615,16 +609,12 @@ class FileListView extends HookConsumerWidget {
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}, childCount: widgetCount),
|
||||
}, childCount: items.length),
|
||||
),
|
||||
// ListView mode
|
||||
_ => SliverList.builder(
|
||||
itemCount: widgetCount,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
|
||||
final item = items[index];
|
||||
return item.map(
|
||||
file:
|
||||
@@ -801,7 +791,7 @@ class FileListView extends HookConsumerWidget {
|
||||
await client.delete(
|
||||
'/drive/index/remove/${fileItem.fileIndex.id}',
|
||||
);
|
||||
ref.invalidate(cloudFileListNotifierProvider);
|
||||
ref.invalidate(indexedCloudFileListNotifierProvider);
|
||||
} catch (e) {
|
||||
showSnackBar('failedToDeleteFile'.tr());
|
||||
} finally {
|
||||
@@ -1010,8 +1000,6 @@ class FileListView extends HookConsumerWidget {
|
||||
|
||||
Widget _buildUnindexedFileListContent(
|
||||
List<FileListItem> items,
|
||||
int widgetCount,
|
||||
Widget endItemView,
|
||||
WidgetRef ref,
|
||||
BuildContext context,
|
||||
ValueNotifier<FileListViewMode> currentViewMode,
|
||||
@@ -1029,10 +1017,6 @@ class FileListView extends HookConsumerWidget {
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
|
||||
if (index >= items.length) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -1067,16 +1051,12 @@ class FileListView extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
);
|
||||
}, childCount: widgetCount),
|
||||
}, childCount: items.length),
|
||||
),
|
||||
// ListView mode
|
||||
_ => SliverList.builder(
|
||||
itemCount: widgetCount,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
|
||||
final item = items[index];
|
||||
return item.map(
|
||||
file: (fileItem) {
|
||||
@@ -1168,7 +1148,7 @@ class FileListView extends HookConsumerWidget {
|
||||
try {
|
||||
final client = ref.read(apiClientProvider);
|
||||
await client.delete('/drive/index/remove/${fileItem.fileIndex.id}');
|
||||
ref.invalidate(cloudFileListNotifierProvider);
|
||||
ref.invalidate(indexedCloudFileListNotifierProvider);
|
||||
} catch (e) {
|
||||
showSnackBar('failedToDeleteFile'.tr());
|
||||
} finally {
|
||||
|
||||
87
lib/widgets/paging/pagination_list.dart
Normal file
87
lib/widgets/paging/pagination_list.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
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:super_sliver_list/super_sliver_list.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;
|
||||
const PaginationList({
|
||||
super.key,
|
||||
required this.provider,
|
||||
required this.notifier,
|
||||
required this.itemBuilder,
|
||||
this.isRefreshable = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final data = ref.watch(provider);
|
||||
final noti = ref.watch(notifier);
|
||||
final listView = SuperListView.builder(
|
||||
itemBuilder: (context, idx) {
|
||||
final entry = data.valueOrNull?[idx];
|
||||
if (entry != null) return itemBuilder(context, idx, entry);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
final child = NotificationListener(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
if (scrollInfo is ScrollEndNotification &&
|
||||
scrollInfo.metrics.axisDirection == AxisDirection.down &&
|
||||
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) {
|
||||
if (!noti.fetchedAll) {
|
||||
noti.fetchFurther();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: listView,
|
||||
);
|
||||
|
||||
return isRefreshable
|
||||
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: child)
|
||||
: child;
|
||||
}
|
||||
}
|
||||
|
||||
class PaginationWidget<T> extends HookConsumerWidget {
|
||||
final ProviderListenable<AsyncValue<List<T>>> provider;
|
||||
final Refreshable<PaginationController<T>> notifier;
|
||||
final Widget Function(List<T>) contentBuilder;
|
||||
final bool isRefreshable;
|
||||
const PaginationWidget({
|
||||
super.key,
|
||||
required this.provider,
|
||||
required this.notifier,
|
||||
required this.contentBuilder,
|
||||
this.isRefreshable = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final data = ref.watch(provider);
|
||||
final noti = ref.watch(notifier);
|
||||
final content = NotificationListener(
|
||||
onNotification: (ScrollNotification scrollInfo) {
|
||||
if (scrollInfo is ScrollEndNotification &&
|
||||
scrollInfo.metrics.axisDirection == AxisDirection.down &&
|
||||
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) {
|
||||
if (!noti.fetchedAll) {
|
||||
noti.fetchFurther();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: contentBuilder(data.valueOrNull ?? []),
|
||||
);
|
||||
|
||||
return isRefreshable
|
||||
? ExtendedRefreshIndicator(onRefresh: noti.refresh, child: content)
|
||||
: content;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.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/services/file_uploader.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
@@ -17,7 +18,6 @@ import 'package:island/pods/network.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/screens/chat/chat.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -664,7 +664,7 @@ class _ChatRoomsList extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final chatRooms = ref.watch(chatroomsJoinedProvider);
|
||||
final chatRooms = ref.watch(chatRoomJoinedNotifierProvider);
|
||||
|
||||
return chatRooms.when(
|
||||
data: (rooms) {
|
||||
|
||||
@@ -124,7 +124,7 @@ class FileItem extends HookConsumerWidget {
|
||||
if (confirmed != true) return;
|
||||
}
|
||||
|
||||
await _showEditSheet(context, ref);
|
||||
if (context.mounted) await _showEditSheet(context, ref);
|
||||
}
|
||||
|
||||
Future<void> _showEditSheet(BuildContext context, WidgetRef ref) async {
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class InfoRow extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final String? value;
|
||||
final IconData icon;
|
||||
final bool monospace;
|
||||
final VoidCallback? onTap;
|
||||
@@ -12,7 +12,7 @@ class InfoRow extends StatelessWidget {
|
||||
const InfoRow({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.value,
|
||||
required this.icon,
|
||||
this.monospace = false,
|
||||
this.onTap,
|
||||
@@ -20,14 +20,17 @@ class InfoRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget valueWidget = Text(
|
||||
value,
|
||||
style:
|
||||
monospace
|
||||
? GoogleFonts.robotoMono(fontSize: 14)
|
||||
: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.end,
|
||||
);
|
||||
Widget? valueWidget =
|
||||
value == null
|
||||
? null
|
||||
: Text(
|
||||
value!,
|
||||
style:
|
||||
monospace
|
||||
? GoogleFonts.robotoMono(fontSize: 14)
|
||||
: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.end,
|
||||
);
|
||||
|
||||
if (onTap != null) valueWidget = InkWell(onTap: onTap, child: valueWidget);
|
||||
|
||||
@@ -40,13 +43,16 @@ class InfoRow extends StatelessWidget {
|
||||
flex: 2,
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style:
|
||||
valueWidget == null
|
||||
? null
|
||||
: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(flex: 3, child: valueWidget),
|
||||
if (valueWidget != null) const Gap(12),
|
||||
if (valueWidget != null) Expanded(flex: 3, child: valueWidget),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
platform :osx, '11.0'
|
||||
platform :osx, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
@@ -214,25 +214,25 @@ PODS:
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.50.4):
|
||||
- sqlite3/common (= 3.50.4)
|
||||
- sqlite3/common (3.50.4)
|
||||
- sqlite3/dbstatvtab (3.50.4):
|
||||
- sqlite3 (3.51.1):
|
||||
- sqlite3/common (= 3.51.1)
|
||||
- sqlite3/common (3.51.1)
|
||||
- sqlite3/dbstatvtab (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/fts5 (3.50.4):
|
||||
- sqlite3/fts5 (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/math (3.50.4):
|
||||
- sqlite3/math (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.50.4):
|
||||
- sqlite3/perf-threadsafe (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.50.4):
|
||||
- sqlite3/rtree (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/session (3.50.4):
|
||||
- sqlite3/session (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.50.4)
|
||||
- sqlite3 (~> 3.51.1)
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/math
|
||||
@@ -456,8 +456,8 @@ SPEC CHECKSUMS:
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
||||
syncfusion_pdfviewer_macos: b3b110c68039178ca4105dd03ef38761eca3b36b
|
||||
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
|
||||
@@ -466,6 +466,6 @@ SPEC CHECKSUMS:
|
||||
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
||||
window_manager: b729e31d38fb04905235df9ea896128991cad99e
|
||||
|
||||
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
||||
PODFILE CHECKSUM: 1e95c36afbfd1cb6423ceca4de7a8e1b256fb6ac
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -613,7 +613,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.4;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@@ -751,7 +751,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.4;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -777,7 +777,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.4;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
||||
76
pubspec.lock
76
pubspec.lock
@@ -1257,10 +1257,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c"
|
||||
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
version: "6.3.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1545,10 +1545,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: local_auth_android
|
||||
sha256: "04dd9050b59cb4bcaf051d44eec65865779a9b2f6daccc523f59f96b565a5d54"
|
||||
sha256: "162b8e177fd9978c4620da2a8002a5c6bed4d20f0c6daf5137e72e9a8b767d2e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
version: "2.0.4"
|
||||
local_auth_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1633,18 +1633,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: material_symbols_icons
|
||||
sha256: "9a7de58ffc299c8e362b4e860e36e1d198fa0981a894376fe1b6bfe52773e15b"
|
||||
sha256: "02555a48e1ec02b16e532dfd4ef13c4f6bf7ec7c20230e58e56641a393433dc3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2874.0"
|
||||
version: "4.2892.0"
|
||||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit
|
||||
sha256: dfd5ab85d49a1806b1314a0b81f3d14da48f0db0a657336b2d77c5f17db28944
|
||||
sha256: "2a207ea7baf1a2ea2ff2016d512e572ca6fc02a937769effb5c27b4d682b4a53"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.3"
|
||||
media_kit_libs_android_video:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1697,10 +1697,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_video
|
||||
sha256: eac9b5f27310afe6def49f9b785cef155f99f1db6079d2b2b127b8bddafb6472
|
||||
sha256: afaa509e7b7e0bf247557a3a740cde903a52c34ace9810f94500e127bd7b043d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.0.1"
|
||||
menu_base:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2354,10 +2354,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "46a46fd64659eff15f4638bbe19de43f9483f0e0bf024a9fb6b3582064bacc7b"
|
||||
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.17"
|
||||
version: "2.4.18"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2543,10 +2543,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df"
|
||||
sha256: "1e800ebe7f85a80a66adacaa6febe4d5f4d8b75f244e9838a27cb2ffc7aec08d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.40"
|
||||
version: "0.5.41"
|
||||
sqlparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2639,18 +2639,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_flutter_core
|
||||
sha256: a55762b7d6fdfe588378127b7b186aad418c04a8670c40d3d3438fa09e3d259a
|
||||
sha256: e68a7e214659faf0df483c760d295ab58e376a639285d2a9f7d1e43351efcbb3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
syncfusion_flutter_pdf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_flutter_pdf
|
||||
sha256: ac85ff223e830f3f9d4f799bc5817e1ce6a136d60b3f8dac28bd9d6860db56e0
|
||||
sha256: "7a09952cc5193f78211a84eed3f0bd67876861997221b2479889013951fb5fba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
syncfusion_flutter_pdfviewer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2663,50 +2663,50 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_flutter_signaturepad
|
||||
sha256: ea5f6d5245f2297d1e6d296bfd43d5bd1ce5a13f8e24b6824fcd58ba9e152f72
|
||||
sha256: f65d43512fccb2733dcee292a36d561e1963c61f86482007d70c0d4a007718f1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
syncfusion_pdfviewer_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_pdfviewer_linux
|
||||
sha256: "9f7c99392acad22d3e837490226ccad85a172a9b0b958a9e6346237cb966c9c8"
|
||||
sha256: "2a80a35c11fd3f06444ce4cad2e7b36ce1cea28030379186a8545851588d1784"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
syncfusion_pdfviewer_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_pdfviewer_macos
|
||||
sha256: "4965ce7c8ccdd6d9cb1248bd87af4c4f69af7e7bdd822e1f13a94e25dc62262b"
|
||||
sha256: "9bf2c720d59539cf2e621b9b5214301c3ae9d4c62c1a5a2e0e9c2b20f9e4727d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
syncfusion_pdfviewer_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_pdfviewer_platform_interface
|
||||
sha256: bc1121557352cb1b9710a79963300799dc6ff129522e04a47fcbc7a958e2feb8
|
||||
sha256: b5eded6e60270422db529549142cc16e927dbb7317afe3559140bf4ec4301ac3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
syncfusion_pdfviewer_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_pdfviewer_web
|
||||
sha256: e4b12a8d9e7d3e867a5d5c5153e94db028b43b52b7b11be3a1c36f49b87c93db
|
||||
sha256: "251e4d43a54958c17ba33bf27a0ca143224663755cac68fe2fe8fb0cce5bd846"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
syncfusion_pdfviewer_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: syncfusion_pdfviewer_windows
|
||||
sha256: "057f85e3978ebb2550121c6b2237db63f3b195b17e53806b1745dcc2c743bc7d"
|
||||
sha256: "33014789595c9782337773e2c891e25fc4b22ede60cc5c95be032765c5d43989"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "31.2.15"
|
||||
version: "31.2.16"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2727,34 +2727,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker
|
||||
sha256: "31b3081b6787c93b41bc14182229681a4a48b8b22f4b368f3493e7f41825a36d"
|
||||
sha256: "5ab800e29d91ce7728fa218c8a7d46f6c228202ac89af650c3f82cb938a64ba6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "5.1.3"
|
||||
talker_dio_logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker_dio_logger
|
||||
sha256: "5534edf33aa913bdc9b4c733baee4795a884e46e6e632b024762738b53622da5"
|
||||
sha256: "214a31d2ecc488ae6abf623ca9dac3831d34e66195f633bd1909a9d0c282ab8c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "5.1.3"
|
||||
talker_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker_flutter
|
||||
sha256: cd276e639841ddcd50effe86bc16e04ff9a58d1a06d470b0c3c7d62f13579ae8
|
||||
sha256: "79158cf0fe3fd2bcdb1dc6f5a870cb623f18d0b59a4fd87414f53ce446d80a45"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "5.1.3"
|
||||
talker_logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker_logger
|
||||
sha256: "5e2cc1dd9cbb7811c44ed620b5365768e30b86b6a65e909a5c2324722517f077"
|
||||
sha256: bc75612ace4dbb82fbad36181ff27e95b1ee152c719c2fea6b8ac59c4f091cd2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "5.1.3"
|
||||
talker_riverpod_logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
16
pubspec.yaml
16
pubspec.yaml
@@ -52,13 +52,13 @@ dependencies:
|
||||
flutter_highlight: ^0.7.0
|
||||
uuid: ^4.5.2
|
||||
url_launcher: ^6.3.2
|
||||
google_fonts: ^6.3.2
|
||||
google_fonts: ^6.3.3
|
||||
gap: ^3.0.1
|
||||
cached_network_image: ^3.4.1
|
||||
web: ^1.1.1
|
||||
flutter_blurhash: ^0.9.1
|
||||
media_kit: ^1.2.2
|
||||
media_kit_video: ^2.0.0
|
||||
media_kit: ^1.2.3
|
||||
media_kit_video: ^2.0.1
|
||||
media_kit_libs_video: ^1.0.7
|
||||
flutter_cache_manager: ^3.4.1
|
||||
|
||||
@@ -83,7 +83,7 @@ dependencies:
|
||||
flutter_udid: ^4.1.1
|
||||
firebase_core: ^4.2.1
|
||||
web_socket_channel: ^3.0.3
|
||||
material_symbols_icons: ^4.2874.0
|
||||
material_symbols_icons: ^4.2892.0
|
||||
drift: ^2.28.2
|
||||
drift_flutter: ^0.2.7
|
||||
path: ^1.9.1
|
||||
@@ -155,10 +155,10 @@ dependencies:
|
||||
dart_ipc: ^1.0.1
|
||||
pretty_diff_text: ^2.1.0
|
||||
window_manager: ^0.5.1
|
||||
talker: ^5.1.1
|
||||
talker_flutter: ^5.1.1
|
||||
talker_logger: ^5.1.1
|
||||
talker_dio_logger: ^5.1.1
|
||||
talker: ^5.1.3
|
||||
talker_flutter: ^5.1.3
|
||||
talker_logger: ^5.1.3
|
||||
talker_dio_logger: ^5.1.3
|
||||
talker_riverpod_logger: ^5.0.1
|
||||
syncfusion_flutter_pdfviewer: ^31.1.21
|
||||
swipe_to: ^1.0.6
|
||||
|
||||
Reference in New Issue
Block a user