Compare commits

..

5 Commits

Author SHA1 Message Date
1c2f25a152 💄 Optimize leveling page 2025-09-24 21:21:51 +08:00
be26ea280e 🌐 Make file info localizable 2025-09-24 21:20:00 +08:00
b4996d069f 🐛 Fix bugs 2025-09-24 21:03:53 +08:00
bf4892b34d 🐛 Fix bugs 2025-09-24 20:52:56 +08:00
5f84751fd5 🐛 Fix file upload 2025-09-24 20:29:30 +08:00
17 changed files with 271 additions and 232 deletions

View File

@@ -264,14 +264,14 @@
"createStickerPack": "Create a Sticker Pack", "createStickerPack": "Create a Sticker Pack",
"editStickerPack": "Edit Sticker Pack", "editStickerPack": "Edit Sticker Pack",
"deleteStickerPack": "Delete Sticker Pack", "deleteStickerPack": "Delete Sticker Pack",
"deleteStickerPackHint": "Are you sure to delete this sticker pack? This action cannot be undone.", "deleteStickerPackHint": "Are you sure you want to delete this sticker pack? This action cannot be undone.",
"stickerPackPrefix": "Prefix", "stickerPackPrefix": "Prefix",
"stickerPackPrefixHint": "The prefix will be added before each stickers' slug in this pack.", "stickerPackPrefixHint": "The prefix will be added before each stickers' slug in this pack.",
"stickers": "Stickers", "stickers": "Stickers",
"createSticker": "Create a Sticker", "createSticker": "Create a Sticker",
"editSticker": "Edit Sticker", "editSticker": "Edit Sticker",
"deleteSticker": "Delete Sticker", "deleteSticker": "Delete Sticker",
"deleteStickerHint": "Are you sure to delete this sticker? This action cannot be undone.", "deleteStickerHint": "Are you sure you want to delete this sticker? This action cannot be undone.",
"stickerImage": "Image", "stickerImage": "Image",
"stickerSlug": "Slug", "stickerSlug": "Slug",
"stickerSlugHint": "The slug will be combined with the prefix to form the sticker's unique identifier.", "stickerSlugHint": "The slug will be combined with the prefix to form the sticker's unique identifier.",
@@ -350,11 +350,11 @@
"levelingStage12": "Transcendent", "levelingStage12": "Transcendent",
"fileUploadingProgress": "Uploading file #{}: {}%", "fileUploadingProgress": "Uploading file #{}: {}%",
"removeChatMember": "Remove Chat Room Member", "removeChatMember": "Remove Chat Room Member",
"removeChatMemberHint": "Are you sure to remove this member from the room?", "removeChatMemberHint": "Are you sure you want to remove this member from the room?",
"removeRealmMember": "Remove Realm Member", "removeRealmMember": "Remove Realm Member",
"removeRealmMemberHint": "Are you sure to remove this member from the realm?", "removeRealmMemberHint": "Are you sure you want to remove this member from the realm?",
"removePublisherMember": "Remove Publisher Member", "removePublisherMember": "Remove Publisher Member",
"removePublisherMemberHint": "Are you sure to remove this member from the publisher?", "removePublisherMemberHint": "Are you sure you want to remove this member from the publisher?",
"memberRole": "Member Role", "memberRole": "Member Role",
"memberRoleHint": "Greater number has higher permission.", "memberRoleHint": "Greater number has higher permission.",
"memberRoleEdit": "Edit role for @{}", "memberRoleEdit": "Edit role for @{}",
@@ -363,9 +363,9 @@
"brokenLink": "Unable open link {}... It might be broken or missing uri parts...", "brokenLink": "Unable open link {}... It might be broken or missing uri parts...",
"copyToClipboard": "Copy to clipboard", "copyToClipboard": "Copy to clipboard",
"leaveChatRoom": "Leave Chat Room", "leaveChatRoom": "Leave Chat Room",
"leaveChatRoomHint": "Are you sure to leave this chat room?", "leaveChatRoomHint": "Are you sure you want to leave this chat room?",
"leaveRealm": "Leave Realm", "leaveRealm": "Leave Realm",
"leaveRealmHint": "Are you sure to leave this realm?", "leaveRealmHint": "Are you sure you want to leave this realm?",
"walletNotFound": "Wallet not found", "walletNotFound": "Wallet not found",
"walletCreateHint": "You don't have a wallet yet. Create one to start using the Solar Network eWallet.", "walletCreateHint": "You don't have a wallet yet. Create one to start using the Solar Network eWallet.",
"walletCreate": "Create a Wallet", "walletCreate": "Create a Wallet",
@@ -468,7 +468,7 @@
"one": "{} is typing...", "one": "{} is typing...",
"other": "{} are typing..." "other": "{} are typing..."
}, },
"authDeviceEditLabel": "Edit Label", "authDeviceEditLabel": "Edit Device Label",
"authDeviceLabelTitle": "Edit Device Label", "authDeviceLabelTitle": "Edit Device Label",
"authDeviceLabelHint": "Enter a name for this device", "authDeviceLabelHint": "Enter a name for this device",
"authDeviceSwipeEditHint": "Swipe left to edit label", "authDeviceSwipeEditHint": "Swipe left to edit label",
@@ -536,7 +536,7 @@
"contactMethodPrimary": "Primary", "contactMethodPrimary": "Primary",
"contactMethodSetPrimary": "Set as Primary", "contactMethodSetPrimary": "Set as Primary",
"contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications", "contactMethodSetPrimaryHint": "Set this contact method as your primary contact method for account recovery and notifications",
"contactMethodDeleteHint": "Are you sure to delete this contact method? This action cannot be undone.", "contactMethodDeleteHint": "Are you sure you want to delete this contact method? This action cannot be undone.",
"contactMethodMakePublic": "Make Public", "contactMethodMakePublic": "Make Public",
"contactMethodMakePrivate": "Make Private", "contactMethodMakePrivate": "Make Private",
"contactMethodPublic": "Public", "contactMethodPublic": "Public",
@@ -662,8 +662,6 @@
"abuseReportSuccess": "Report submitted successfully. Thank you for helping keep our community safe.", "abuseReportSuccess": "Report submitted successfully. Thank you for helping keep our community safe.",
"abuseReportError": "Failed to submit report. Please try again.", "abuseReportError": "Failed to submit report. Please try again.",
"abuseReportReasonRequired": "Please provide details about the issue", "abuseReportReasonRequired": "Please provide details about the issue",
"abuseReportSuccessTitle": "Report Submitted",
"abuseReportErrorTitle": "Error",
"abuseReportTypeSpam": "Spam or Misleading", "abuseReportTypeSpam": "Spam or Misleading",
"abuseReportTypeHarassment": "Harassment or Abuse", "abuseReportTypeHarassment": "Harassment or Abuse",
"abuseReportTypeInappropriate": "Inappropriate Content", "abuseReportTypeInappropriate": "Inappropriate Content",
@@ -845,11 +843,6 @@
"postCategorySports": "Sports", "postCategorySports": "Sports",
"postCategoryFinance": "Finance", "postCategoryFinance": "Finance",
"postCategoryLife": "Life", "postCategoryLife": "Life",
"postCategoryArt": "Art",
"postCategoryStudy": "Study",
"postCategoryGaming": "Gaming",
"postCategoryProgramming": "Programming",
"postCategoryMusic": "Music",
"links": "Links", "links": "Links",
"addLink": "Add link", "addLink": "Add link",
"linkKey": "Link Name", "linkKey": "Link Name",
@@ -1062,5 +1055,12 @@
"deleteRecycledFiles": "Delete Recycled Files", "deleteRecycledFiles": "Delete Recycled Files",
"recycledFilesDeleted": "Recycled files deleted successfully", "recycledFilesDeleted": "Recycled files deleted successfully",
"failedToDeleteRecycledFiles": "Failed to delete recycled files", "failedToDeleteRecycledFiles": "Failed to delete recycled files",
"upload": "Upload" "upload": "Upload",
"fileInfoTitle": "File Information",
"fileHashCopied": "File hash copied to clipboard",
"fileIdCopied": "File ID copied to clipboard",
"fileNameCopied": "File name copied to clipboard",
"fileMetadata": "File Metadata",
"userMetadata": "User Metadata",
"valueCopied": "Value copied to clipboard"
} }

View File

@@ -33,17 +33,27 @@ class AppDatabase extends _$AppDatabase {
await _migrateToVersion6(m); await _migrateToVersion6(m);
} }
if (from < 7) { if (from < 7) {
// Add new columns from SnChatMessage // Add new columns from SnChatMessage, ignore if they already exist
await m.addColumn(chatMessages, chatMessages.updatedAt); final columnsToAdd = [
await m.addColumn(chatMessages, chatMessages.deletedAt); chatMessages.updatedAt,
await m.addColumn(chatMessages, chatMessages.type); chatMessages.deletedAt,
await m.addColumn(chatMessages, chatMessages.meta); chatMessages.type,
await m.addColumn(chatMessages, chatMessages.membersMentioned); chatMessages.meta,
await m.addColumn(chatMessages, chatMessages.editedAt); chatMessages.membersMentioned,
await m.addColumn(chatMessages, chatMessages.attachments); chatMessages.editedAt,
await m.addColumn(chatMessages, chatMessages.reactions); chatMessages.attachments,
await m.addColumn(chatMessages, chatMessages.repliedMessageId); chatMessages.reactions,
await m.addColumn(chatMessages, chatMessages.forwardedMessageId); chatMessages.repliedMessageId,
chatMessages.forwardedMessageId,
];
for (final column in columnsToAdd) {
try {
await m.addColumn(chatMessages, column);
} catch (e) {
// Column already exists, skip
}
}
} }
}, },
); );

View File

@@ -1,4 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file_pool.dart';
part 'file.freezed.dart'; part 'file.freezed.dart';
part 'file.g.dart'; part 'file.g.dart';
@@ -42,6 +43,7 @@ sealed class SnCloudFile with _$SnCloudFile {
required String? description, required String? description,
required Map<String, dynamic>? fileMeta, required Map<String, dynamic>? fileMeta,
required Map<String, dynamic>? userMeta, required Map<String, dynamic>? userMeta,
required SnFilePool? pool,
@Default([]) List<int> sensitiveMarks, @Default([]) List<int> sensitiveMarks,
required String? mimeType, required String? mimeType,
required String? hash, required String? hash,

View File

@@ -278,7 +278,7 @@ as bool,
/// @nodoc /// @nodoc
mixin _$SnCloudFile { mixin _$SnCloudFile {
String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; List<int> get sensitiveMarks; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get name; String? get description; Map<String, dynamic>? get fileMeta; Map<String, dynamic>? get userMeta; SnFilePool? get pool; List<int> get sensitiveMarks; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnCloudFile /// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -291,16 +291,16 @@ $SnCloudFileCopyWith<SnCloudFile> get copyWith => _$SnCloudFileCopyWithImpl<SnCl
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&const DeepCollectionEquality().equals(other.sensitiveMarks, sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(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 SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other.fileMeta, fileMeta)&&const DeepCollectionEquality().equals(other.userMeta, userMeta)&&(identical(other.pool, pool) || other.pool == pool)&&const DeepCollectionEquality().equals(other.sensitiveMarks, sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),const DeepCollectionEquality().hash(sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt); int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),pool,const DeepCollectionEquality().hash(sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
@override @override
String toString() { String toString() {
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, pool: $pool, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@@ -311,11 +311,11 @@ abstract mixin class $SnCloudFileCopyWith<$Res> {
factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl; factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, SnFilePool? pool, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
$SnFilePoolCopyWith<$Res>? get pool;
} }
/// @nodoc /// @nodoc
@@ -328,14 +328,15 @@ class _$SnCloudFileCopyWithImpl<$Res>
/// Create a copy of SnCloudFile /// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? pool = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,fileMeta: freezed == fileMeta ? _self.fileMeta : fileMeta // ignore: cast_nullable_to_non_nullable as String?,fileMeta: freezed == fileMeta ? _self.fileMeta : fileMeta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self.userMeta : userMeta // ignore: cast_nullable_to_non_nullable as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self.userMeta : userMeta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,sensitiveMarks: null == sensitiveMarks ? _self.sensitiveMarks : sensitiveMarks // ignore: cast_nullable_to_non_nullable as Map<String, dynamic>?,pool: freezed == pool ? _self.pool : pool // ignore: cast_nullable_to_non_nullable
as SnFilePool?,sensitiveMarks: null == sensitiveMarks ? _self.sensitiveMarks : sensitiveMarks // ignore: cast_nullable_to_non_nullable
as List<int>,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable as List<int>,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
@@ -347,7 +348,19 @@ as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ign
as DateTime?, as DateTime?,
)); ));
} }
/// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnFilePoolCopyWith<$Res>? get pool {
if (_self.pool == null) {
return null;
}
return $SnFilePoolCopyWith<$Res>(_self.pool!, (value) {
return _then(_self.copyWith(pool: value));
});
}
} }
@@ -426,10 +439,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, SnFilePool? pool, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _SnCloudFile() when $default != null: case _SnCloudFile() when $default != null:
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse(); return orElse();
} }
@@ -447,10 +460,10 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, SnFilePool? pool, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnCloudFile(): case _SnCloudFile():
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);} return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///
@@ -464,10 +477,10 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, SnFilePool? pool, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _SnCloudFile() when $default != null: case _SnCloudFile() when $default != null:
return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null; return null;
} }
@@ -479,7 +492,7 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM
@JsonSerializable() @JsonSerializable()
class _SnCloudFile implements SnCloudFile { class _SnCloudFile implements SnCloudFile {
const _SnCloudFile({required this.id, required this.name, required this.description, required final Map<String, dynamic>? fileMeta, required final Map<String, dynamic>? userMeta, final List<int> sensitiveMarks = const [], required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta,_sensitiveMarks = sensitiveMarks; const _SnCloudFile({required this.id, required this.name, required this.description, required final Map<String, dynamic>? fileMeta, required final Map<String, dynamic>? userMeta, required this.pool, final List<int> sensitiveMarks = const [], required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta,_sensitiveMarks = sensitiveMarks;
factory _SnCloudFile.fromJson(Map<String, dynamic> json) => _$SnCloudFileFromJson(json); factory _SnCloudFile.fromJson(Map<String, dynamic> json) => _$SnCloudFileFromJson(json);
@override final String id; @override final String id;
@@ -503,6 +516,7 @@ class _SnCloudFile implements SnCloudFile {
return EqualUnmodifiableMapView(value); return EqualUnmodifiableMapView(value);
} }
@override final SnFilePool? pool;
final List<int> _sensitiveMarks; final List<int> _sensitiveMarks;
@override@JsonKey() List<int> get sensitiveMarks { @override@JsonKey() List<int> get sensitiveMarks {
if (_sensitiveMarks is EqualUnmodifiableListView) return _sensitiveMarks; if (_sensitiveMarks is EqualUnmodifiableListView) return _sensitiveMarks;
@@ -532,16 +546,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&const DeepCollectionEquality().equals(other._sensitiveMarks, _sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(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 _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.pool, pool) || other.pool == pool)&&const DeepCollectionEquality().equals(other._sensitiveMarks, _sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),const DeepCollectionEquality().hash(_sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt); int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),pool,const DeepCollectionEquality().hash(_sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt);
@override @override
String toString() { String toString() {
return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, pool: $pool, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@@ -552,11 +566,11 @@ abstract mixin class _$SnCloudFileCopyWith<$Res> implements $SnCloudFileCopyWith
factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl; factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String name, String? description, Map<String, dynamic>? fileMeta, Map<String, dynamic>? userMeta, SnFilePool? pool, List<int> sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
}); });
@override $SnFilePoolCopyWith<$Res>? get pool;
} }
/// @nodoc /// @nodoc
@@ -569,14 +583,15 @@ class __$SnCloudFileCopyWithImpl<$Res>
/// Create a copy of SnCloudFile /// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? pool = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnCloudFile( return _then(_SnCloudFile(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,fileMeta: freezed == fileMeta ? _self._fileMeta : fileMeta // ignore: cast_nullable_to_non_nullable as String?,fileMeta: freezed == fileMeta ? _self._fileMeta : fileMeta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self._userMeta : userMeta // ignore: cast_nullable_to_non_nullable as Map<String, dynamic>?,userMeta: freezed == userMeta ? _self._userMeta : userMeta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,sensitiveMarks: null == sensitiveMarks ? _self._sensitiveMarks : sensitiveMarks // ignore: cast_nullable_to_non_nullable as Map<String, dynamic>?,pool: freezed == pool ? _self.pool : pool // ignore: cast_nullable_to_non_nullable
as SnFilePool?,sensitiveMarks: null == sensitiveMarks ? _self._sensitiveMarks : sensitiveMarks // ignore: cast_nullable_to_non_nullable
as List<int>,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable as List<int>,mimeType: freezed == mimeType ? _self.mimeType : mimeType // ignore: cast_nullable_to_non_nullable
as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable as String?,hash: freezed == hash ? _self.hash : hash // ignore: cast_nullable_to_non_nullable
as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable as String?,size: null == size ? _self.size : size // ignore: cast_nullable_to_non_nullable
@@ -589,7 +604,19 @@ as DateTime?,
)); ));
} }
/// Create a copy of SnCloudFile
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnFilePoolCopyWith<$Res>? get pool {
if (_self.pool == null) {
return null;
}
return $SnFilePoolCopyWith<$Res>(_self.pool!, (value) {
return _then(_self.copyWith(pool: value));
});
}
} }
// dart format on // dart format on

View File

@@ -33,6 +33,10 @@ _SnCloudFile _$SnCloudFileFromJson(Map<String, dynamic> json) => _SnCloudFile(
description: json['description'] as String?, description: json['description'] as String?,
fileMeta: json['file_meta'] as Map<String, dynamic>?, fileMeta: json['file_meta'] as Map<String, dynamic>?,
userMeta: json['user_meta'] as Map<String, dynamic>?, userMeta: json['user_meta'] as Map<String, dynamic>?,
pool:
json['pool'] == null
? null
: SnFilePool.fromJson(json['pool'] as Map<String, dynamic>),
sensitiveMarks: sensitiveMarks:
(json['sensitive_marks'] as List<dynamic>?) (json['sensitive_marks'] as List<dynamic>?)
?.map((e) => (e as num).toInt()) ?.map((e) => (e as num).toInt())
@@ -61,6 +65,7 @@ Map<String, dynamic> _$SnCloudFileToJson(_SnCloudFile instance) =>
'description': instance.description, 'description': instance.description,
'file_meta': instance.fileMeta, 'file_meta': instance.fileMeta,
'user_meta': instance.userMeta, 'user_meta': instance.userMeta,
'pool': instance.pool?.toJson(),
'sensitive_marks': instance.sensitiveMarks, 'sensitive_marks': instance.sensitiveMarks,
'mime_type': instance.mimeType, 'mime_type': instance.mimeType,
'hash': instance.hash, 'hash': instance.hash,

View File

@@ -23,31 +23,3 @@ sealed class SnFilePool with _$SnFilePool {
factory SnFilePool.fromJson(Map<String, dynamic> json) => factory SnFilePool.fromJson(Map<String, dynamic> json) =>
_$SnFilePoolFromJson(json); _$SnFilePoolFromJson(json);
} }
extension SnFilePoolList on List<SnFilePool> {
static List<SnFilePool> listFromResponse(dynamic data) {
if (data is List) {
return data
.whereType<Map<String, dynamic>>()
.map(SnFilePool.fromJson)
.toList();
}
throw ArgumentError('Unexpected response format: $data');
}
List<SnFilePool> filterValid() {
return where((p) {
final accept = p.policyConfig?['accept_types'];
if (accept is List) {
final acceptsOnlyMedia = accept.every((t) =>
t is String &&
(t.startsWith('image/') ||
t.startsWith('video/') ||
t.startsWith('audio/')));
if (acceptsOnlyMedia) return false;
}
return true;
}).toList();
}
}

View File

@@ -6,23 +6,19 @@ import 'package:island/pods/network.dart';
final poolsProvider = FutureProvider<List<SnFilePool>>((ref) async { final poolsProvider = FutureProvider<List<SnFilePool>>((ref) async {
final dio = ref.watch(apiClientProvider); final dio = ref.watch(apiClientProvider);
final response = await dio.get('/drive/pools'); final response = await dio.get('/drive/pools');
final pools = SnFilePoolList.listFromResponse(response.data); return response.data
return pools.filterValid(); .map((e) => SnFilePool.fromJson(e))
.cast<SnFilePool>()
.toList();
}); });
String resolveDefaultPoolId(WidgetRef ref, List<SnFilePool> pools) { String? resolveDefaultPoolId(WidgetRef ref, List<SnFilePool> pools) {
final settings = ref.watch(appSettingsNotifierProvider); final settings = ref.watch(appSettingsNotifierProvider);
final validPools = pools.filterValid();
final configuredId = settings.defaultPoolId; final configuredId = settings.defaultPoolId;
if (configuredId != null && validPools.any((p) => p.id == configuredId)) { if (configuredId != null && pools.any((p) => p.id == configuredId)) {
return configuredId; return configuredId;
} }
if (validPools.isNotEmpty) { return pools.firstOrNull?.id;
return validPools.first.id; }
}
// DEFAULT: Solar Network Driver
return '500e5ed8-bd44-4359-bc0a-ec85e2adf447'; }

View File

@@ -10,17 +10,19 @@ Future<void> resetDatabase(WidgetRef ref) async {
if (kIsWeb) return; if (kIsWeb) return;
final db = ref.read(databaseProvider); final db = ref.read(databaseProvider);
final basepath = await getApplicationSupportDirectory();
final file = File(join(basepath.path, 'solar_network_data.sqlite'));
// Close current database connection // Close current database connection
db.close(); await db.close();
// Delete database file // Get the correct database file path
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(join(dbFolder.path, 'solar_network_data.sqlite'));
// Delete database file if it exists
if (await file.exists()) { if (await file.exists()) {
await file.delete(); await file.delete();
} }
// Force refresh the database provider // Force refresh the database provider to create a new instance
ref.invalidate(databaseProvider); ref.invalidate(databaseProvider);
} }

View File

@@ -148,7 +148,6 @@ class LevelingScreen extends HookConsumerWidget {
return Center( return Center(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
constraints: const BoxConstraints(maxWidth: 480),
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
const SliverGap(20), const SliverGap(20),
@@ -180,6 +179,12 @@ class LevelingScreen extends HookConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text(
'${'levelingProgressLevel'.tr(args: [currentLevel.toString()])} / 120',
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.bodySmall,
),
const Gap(8),
LinearProgressIndicator( LinearProgressIndicator(
value: currentLevel / 120, value: currentLevel / 120,
minHeight: 10, minHeight: 10,
@@ -190,12 +195,6 @@ class LevelingScreen extends HookConsumerWidget {
Theme.of(context).colorScheme.surfaceContainerHigh, Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
), ),
const Gap(8),
Text(
'${'levelingProgressLevel'.tr(args: [currentLevel.toString()])} / 120',
textAlign: TextAlign.right,
style: Theme.of(context).textTheme.bodySmall,
),
], ],
).padding(horizontal: 16, top: 16, bottom: 12), ).padding(horizontal: 16, top: 16, bottom: 12),
), ),
@@ -272,9 +271,6 @@ class LevelingScreen extends HookConsumerWidget {
return SingleChildScrollView( return SingleChildScrollView(
padding: getTabbedPadding(context, horizontal: 20, vertical: 20), padding: getTabbedPadding(context, horizontal: 20, vertical: 20),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
@@ -282,8 +278,6 @@ class LevelingScreen extends HookConsumerWidget {
const Gap(16), const Gap(16),
], ],
), ),
),
),
); );
} }

View File

@@ -935,7 +935,7 @@ class _ChatAttachmentUploaderSheetState
if (snapshot.hasError) { if (snapshot.hasError) {
return Center(child: Text('errorLoadingPools'.tr())); return Center(child: Text('errorLoadingPools'.tr()));
} }
final pools = snapshot.data!.filterValid(); final pools = snapshot.data!;
selectedPoolId ??= resolveDefaultPoolId(widget.ref, pools); selectedPoolId ??= resolveDefaultPoolId(widget.ref, pools);
return Column( return Column(
@@ -1162,9 +1162,7 @@ class _ChatAttachmentUploaderSheetState
// Get the selected pool to check constraints // Get the selected pool to check constraints
final pools = await widget.ref.read(poolsProvider.future); final pools = await widget.ref.read(poolsProvider.future);
final selectedPool = pools.filterValid().firstWhere( final selectedPool = pools.firstWhere((p) => p.id == selectedPoolId);
(p) => p.id == selectedPoolId,
);
// Check constraints // Check constraints
final maxFileSize = selectedPool.policyConfig?['max_file_size'] as int?; final maxFileSize = selectedPool.policyConfig?['max_file_size'] as int?;

View File

@@ -6,7 +6,7 @@ part of 'file_list.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$billingUsageHash() => r'270ec8499378ee0c038aa44ad1c2e3ad9025740a'; String _$billingUsageHash() => r'58d8bc774868d60781574c85d6b25869a79c57aa';
/// See also [billingUsage]. /// See also [billingUsage].
@ProviderFor(billingUsage) @ProviderFor(billingUsage)
@@ -25,7 +25,7 @@ final billingUsageProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef BillingUsageRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>; typedef BillingUsageRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>;
String _$billingQuotaHash() => r'0696b500fa8bb1270641bcacf262be58caff9b38'; String _$billingQuotaHash() => r'4ec5d728e439015800abb2d0d673b5a7329cc654';
/// See also [billingQuota]. /// See also [billingQuota].
@ProviderFor(billingQuota) @ProviderFor(billingQuota)
@@ -45,7 +45,7 @@ final billingQuotaProvider =
// ignore: unused_element // ignore: unused_element
typedef BillingQuotaRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>; typedef BillingQuotaRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>;
String _$cloudFileListNotifierHash() => String _$cloudFileListNotifierHash() =>
r'e2c8a076a9e635c7b43a87d00f78775427ba6334'; r'22c45a8ea23147a3835ba870ad2f0bb833f853ea';
/// See also [CloudFileListNotifier]. /// See also [CloudFileListNotifier].
@ProviderFor(CloudFileListNotifier) @ProviderFor(CloudFileListNotifier)

View File

@@ -22,7 +22,6 @@ import 'package:path_provider/path_provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
import 'package:island/pods/file_pool.dart'; import 'package:island/pods/file_pool.dart';
import 'package:island/models/file_pool.dart';
class SettingsScreen extends HookConsumerWidget { class SettingsScreen extends HookConsumerWidget {
const SettingsScreen({super.key}); const SettingsScreen({super.key});
@@ -417,7 +416,7 @@ class SettingsScreen extends HookConsumerWidget {
if (user.value != null) if (user.value != null)
pools.when( pools.when(
data: (data) { data: (data) {
final validPools = data.filterValid(); final validPools = data;
final currentPoolId = resolveDefaultPoolId(ref, data); final currentPoolId = resolveDefaultPoolId(ref, data);
return ListTile( return ListTile(
@@ -437,11 +436,14 @@ class SettingsScreen extends HookConsumerWidget {
validPools.map((p) { validPools.map((p) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: p.id, value: p.id,
child: Tooltip(
message: p.name,
child: Text( child: Text(
p.name, p.name,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
).fontSize(14), ).fontSize(14),
),
); );
}).toList(), }).toList(),
value: currentPoolId, value: currentPoolId,

View File

@@ -147,6 +147,7 @@ class AccountProfileCard extends HookConsumerWidget {
if (data.badges.isNotEmpty) if (data.badges.isNotEmpty)
BadgeList(badges: data.badges).padding(top: 12), BadgeList(badges: data.badges).padding(top: 12),
LevelingProgressCard( LevelingProgressCard(
isCompact: true,
level: data.profile.level, level: data.profile.level,
experience: data.profile.experience, experience: data.profile.experience,
progress: data.profile.levelingProgress, progress: data.profile.levelingProgress,

View File

@@ -59,7 +59,7 @@ class _AttachmentUploaderSheetState extends State<AttachmentUploaderSheet> {
if (snapshot.hasError) { if (snapshot.hasError) {
return Center(child: Text('errorLoadingPools'.tr())); return Center(child: Text('errorLoadingPools'.tr()));
} }
final pools = snapshot.data!.filterValid(); final pools = snapshot.data!;
selectedPoolId ??= resolveDefaultPoolId(widget.ref, pools); selectedPoolId ??= resolveDefaultPoolId(widget.ref, pools);
return Column( return Column(
@@ -286,9 +286,7 @@ class _AttachmentUploaderSheetState extends State<AttachmentUploaderSheet> {
// Get the selected pool to check constraints // Get the selected pool to check constraints
final pools = await widget.ref.read(poolsProvider.future); final pools = await widget.ref.read(poolsProvider.future);
final selectedPool = pools.filterValid().firstWhere( final selectedPool = pools.firstWhere((p) => p.id == selectedPoolId);
(p) => p.id == selectedPoolId,
);
// Check constraints // Check constraints
final maxFileSize = selectedPool.policyConfig?['max_file_size'] as int?; final maxFileSize = selectedPool.policyConfig?['max_file_size'] as int?;

View File

@@ -130,7 +130,7 @@ class MessageItem extends HookConsumerWidget {
useEffect(() { useEffect(() {
if (flashing) { if (flashing) {
if (flashTimer.value != null) return null; flashTimer.value?.cancel();
isFlashing.value = true; isFlashing.value = true;
flashTimer.value = Timer.periodic( flashTimer.value = Timer.periodic(
const Duration(milliseconds: kFlashDuration), const Duration(milliseconds: kFlashDuration),
@@ -343,6 +343,10 @@ class MessageItemDisplayBubble extends HookConsumerWidget {
isCurrentUser isCurrentUser
? Theme.of(context).colorScheme.onPrimaryContainer ? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onSurfaceVariant; : Theme.of(context).colorScheme.onSurfaceVariant;
final containerColor =
isCurrentUser
? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5)
: Theme.of(context).colorScheme.surfaceContainer;
final hasBackground = final hasBackground =
ref.watch(backgroundImageFileProvider).valueOrNull != null; ref.watch(backgroundImageFileProvider).valueOrNull != null;
@@ -377,6 +381,15 @@ class MessageItemDisplayBubble extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Flexible( Flexible(
child: Container(
decoration: BoxDecoration(
color: containerColor,
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -471,6 +484,7 @@ class MessageItemDisplayBubble extends HookConsumerWidget {
], ],
), ),
), ),
),
MessageIndicators( MessageIndicators(
editedAt: remoteMessage.editedAt, editedAt: remoteMessage.editedAt,
status: message.status, status: message.status,

View File

@@ -21,7 +21,7 @@ class FileInfoSheet extends StatelessWidget {
final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {}; final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {};
return SheetScaffold( return SheetScaffold(
titleText: 'File Information', titleText: 'fileInfoTitle'.tr(),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -81,7 +81,7 @@ class FileInfoSheet extends StatelessWidget {
), ),
onLongPress: () { onLongPress: () {
Clipboard.setData(ClipboardData(text: item.hash!)); Clipboard.setData(ClipboardData(text: item.hash!));
showSnackBar('File hash copied to clipboard'); showSnackBar('fileHashCopied'.tr());
}, },
), ),
), ),
@@ -101,7 +101,7 @@ class FileInfoSheet extends StatelessWidget {
icon: const Icon(Icons.copy), icon: const Icon(Icons.copy),
onPressed: () { onPressed: () {
Clipboard.setData(ClipboardData(text: item.id)); Clipboard.setData(ClipboardData(text: item.id));
showSnackBar('File ID copied to clipboard'); showSnackBar('fileIdCopied'.tr());
}, },
), ),
), ),
@@ -118,7 +118,25 @@ class FileInfoSheet extends StatelessWidget {
icon: const Icon(Icons.copy), icon: const Icon(Icons.copy),
onPressed: () { onPressed: () {
Clipboard.setData(ClipboardData(text: item.name)); Clipboard.setData(ClipboardData(text: item.name));
showSnackBar('File name copied to clipboard'); showSnackBar('fileNameCopied'.tr());
},
),
),
if (item.pool != null)
ListTile(
leading: const Icon(Symbols.calendar_today),
title: Text('File Pool').tr(),
subtitle: Text(
item.pool!.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
trailing: IconButton(
icon: const Icon(Icons.copy),
onPressed: () {
Clipboard.setData(ClipboardData(text: item.pool!.id));
showSnackBar('fileNameCopied'.tr());
}, },
), ),
), ),
@@ -163,7 +181,7 @@ class FileInfoSheet extends StatelessWidget {
Clipboard.setData( Clipboard.setData(
ClipboardData(text: '${entry.value}'), ClipboardData(text: '${entry.value}'),
); );
showSnackBar('Value copied to clipboard'); showSnackBar('valueCopied'.tr());
}, },
), ),
), ),
@@ -180,7 +198,7 @@ class FileInfoSheet extends StatelessWidget {
child: ExpansionTile( child: ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 24), tilePadding: const EdgeInsets.symmetric(horizontal: 24),
title: Text( title: Text(
'File Metadata', 'fileMetadata'.tr(),
style: theme.textTheme.titleMedium?.copyWith( style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -212,7 +230,7 @@ class FileInfoSheet extends StatelessWidget {
Clipboard.setData( Clipboard.setData(
ClipboardData(text: jsonEncode(entry.value)), ClipboardData(text: jsonEncode(entry.value)),
); );
showSnackBar('Value copied to clipboard'); showSnackBar('valueCopied'.tr());
}, },
), ),
), ),
@@ -229,7 +247,7 @@ class FileInfoSheet extends StatelessWidget {
child: ExpansionTile( child: ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 24), tilePadding: const EdgeInsets.symmetric(horizontal: 24),
title: Text( title: Text(
'User Metadata', 'userMetadata'.tr(),
style: theme.textTheme.titleMedium?.copyWith( style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -261,7 +279,7 @@ class FileInfoSheet extends StatelessWidget {
Clipboard.setData( Clipboard.setData(
ClipboardData(text: jsonEncode(entry.value)), ClipboardData(text: jsonEncode(entry.value)),
); );
showSnackBar('Value copied to clipboard'); showSnackBar('valueCopied'.tr());
}, },
), ),
), ),

View File

@@ -1,6 +1,6 @@
; ================================================== ; ==================================================
#define AppVersion "3.2.0" #define AppVersion "3.2.0"
#define BuildNumber "132" #define BuildNumber "134"
; ================================================== ; ==================================================
#define FullVersion AppVersion + "." + BuildNumber #define FullVersion AppVersion + "." + BuildNumber