Pinned chat room

This commit is contained in:
2025-12-22 00:04:23 +08:00
parent 2df31e4244
commit a481b1b82f
11 changed files with 1074 additions and 472 deletions

View File

@@ -697,9 +697,9 @@
"articleAttachmentHint": "Attachments must be uploaded and inserted into the article body to be visible.",
"postVisibility": "Post Visibility",
"currentMembershipMember": "A member of Stellar Program · {}",
"membershipPriceStellar": "1200 NSP per month, level 3+ required",
"membershipPriceNova": "2400 NSP per month, level 6+ required",
"membershipPriceSupernova": "3600 NSP per month, level 9+ required",
"membershipPriceStellar": "1200 NSP per month, level 20+ required",
"membershipPriceNova": "2400 NSP per month, level 40+ required",
"membershipPriceSupernova": "3600 NSP per month, level 60+ required",
"sharePostPhoto": "Share Post as Photo",
"wouldYouLikeToNavigateToChat": "Would You like to navigate to the chat?",
"abuseReports": "Abuse Reports",
@@ -1528,5 +1528,10 @@
"postTagsCategories": "Post Tags and Categories",
"postTagsCategoriesDescription": "Browse posts by category and tags.",
"debugLogs": "Debug Logs",
"debugLogsDescription": "View debug logs for troubleshooting."
"debugLogsDescription": "View debug logs for troubleshooting.",
"pinChatRoom": "Pin Chat Room",
"pinChatRoomDescription": "Pin this chat room to the top.",
"chatRoomPinned": "Chat room pinned successfully.",
"chatRoomUnpinned": "Chat room unpinned successfully.",
"pinnedChatRoom": "Pinned Rooms"
}

View File

@@ -17,7 +17,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase(super.e);
@override
int get schemaVersion => 10;
int get schemaVersion => 12;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -79,6 +79,30 @@ class AppDatabase extends _$AppDatabase {
await m.createTable(realms);
// The realmId column in chat_rooms already exists, just need to ensure the foreign key constraint
}
if (from < 11) {
// Add isPinned column to chat_rooms table
await customStatement(
'ALTER TABLE chat_rooms ADD COLUMN is_pinned INTEGER DEFAULT 0',
);
}
if (from < 12) {
// Add new columns to realms table
await customStatement(
'ALTER TABLE realms ADD COLUMN slug TEXT NOT NULL DEFAULT \'\'',
);
await customStatement(
'ALTER TABLE realms ADD COLUMN verified_as TEXT NULL',
);
await customStatement(
'ALTER TABLE realms ADD COLUMN verified_at DATETIME NULL',
);
await customStatement(
'ALTER TABLE realms ADD COLUMN is_community INTEGER NOT NULL DEFAULT 0',
);
await customStatement(
'ALTER TABLE realms ADD COLUMN is_public INTEGER NOT NULL DEFAULT 0',
);
}
},
);
@@ -341,6 +365,7 @@ class AppDatabase extends _$AppDatabase {
picture: Value(room.picture?.toJson()),
background: Value(room.background?.toJson()),
realmId: Value(room.realmId),
accountId: Value(room.accountId),
createdAt: Value(room.createdAt),
updatedAt: Value(room.updatedAt),
deletedAt: Value(room.deletedAt),
@@ -367,8 +392,13 @@ class AppDatabase extends _$AppDatabase {
RealmsCompanion companionFromRealm(SnRealm realm) {
return RealmsCompanion(
id: Value(realm.id),
slug: Value(realm.slug),
name: Value(realm.name),
description: Value(realm.description),
verifiedAs: Value(realm.verifiedAs),
verifiedAt: Value(realm.verifiedAt),
isCommunity: Value(realm.isCommunity),
isPublic: Value(realm.isPublic),
picture: Value(realm.picture?.toJson()),
background: Value(realm.background?.toJson()),
accountId: Value(realm.accountId),
@@ -422,11 +452,17 @@ class AppDatabase extends _$AppDatabase {
});
// 3. Upsert remote rooms
await batch((batch) {
await batch((batch) async {
for (final room in rooms) {
// Preserve local isPinned status
final currentRoom = await (select(
chatRooms,
)..where((r) => r.id.equals(room.id))).getSingleOrNull();
final isPinned = currentRoom?.isPinned ?? false;
batch.insert(
chatRooms,
companionFromRoom(room),
companionFromRoom(room).copyWith(isPinned: Value(isPinned)),
mode: InsertMode.insertOrReplace,
);
for (final member in room.members ?? []) {
@@ -502,4 +538,16 @@ class AppDatabase extends _$AppDatabase {
// Then save the message
return await saveMessage(messageToCompanion(message));
}
Future<void> toggleChatRoomPinned(String roomId) async {
final room = await (select(
chatRooms,
)..where((r) => r.id.equals(roomId))).getSingleOrNull();
if (room != null) {
final newPinnedStatus = !(room.isPinned ?? false);
await (update(chatRooms)..where((r) => r.id.equals(roomId))).write(
ChatRoomsCompanion(isPinned: Value(newPinnedStatus)),
);
}
}
}

View File

@@ -17,6 +17,15 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _slugMeta = const VerificationMeta('slug');
@override
late final GeneratedColumn<String> slug = GeneratedColumn<String>(
'slug',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String> name = GeneratedColumn<String>(
@@ -37,6 +46,56 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _verifiedAsMeta = const VerificationMeta(
'verifiedAs',
);
@override
late final GeneratedColumn<String> verifiedAs = GeneratedColumn<String>(
'verified_as',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _verifiedAtMeta = const VerificationMeta(
'verifiedAt',
);
@override
late final GeneratedColumn<DateTime> verifiedAt = GeneratedColumn<DateTime>(
'verified_at',
aliasedName,
true,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
);
static const VerificationMeta _isCommunityMeta = const VerificationMeta(
'isCommunity',
);
@override
late final GeneratedColumn<bool> isCommunity = GeneratedColumn<bool>(
'is_community',
aliasedName,
false,
type: DriftSqlType.bool,
requiredDuringInsert: true,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("is_community" IN (0, 1))',
),
);
static const VerificationMeta _isPublicMeta = const VerificationMeta(
'isPublic',
);
@override
late final GeneratedColumn<bool> isPublic = GeneratedColumn<bool>(
'is_public',
aliasedName,
false,
type: DriftSqlType.bool,
requiredDuringInsert: true,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("is_public" IN (0, 1))',
),
);
@override
late final GeneratedColumnWithTypeConverter<Map<String, dynamic>?, String>
picture = GeneratedColumn<String>(
@@ -102,8 +161,13 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
@override
List<GeneratedColumn> get $columns => [
id,
slug,
name,
description,
verifiedAs,
verifiedAt,
isCommunity,
isPublic,
picture,
background,
accountId,
@@ -128,6 +192,14 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
} else if (isInserting) {
context.missing(_idMeta);
}
if (data.containsKey('slug')) {
context.handle(
_slugMeta,
slug.isAcceptableOrUnknown(data['slug']!, _slugMeta),
);
} else if (isInserting) {
context.missing(_slugMeta);
}
if (data.containsKey('name')) {
context.handle(
_nameMeta,
@@ -143,6 +215,37 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
),
);
}
if (data.containsKey('verified_as')) {
context.handle(
_verifiedAsMeta,
verifiedAs.isAcceptableOrUnknown(data['verified_as']!, _verifiedAsMeta),
);
}
if (data.containsKey('verified_at')) {
context.handle(
_verifiedAtMeta,
verifiedAt.isAcceptableOrUnknown(data['verified_at']!, _verifiedAtMeta),
);
}
if (data.containsKey('is_community')) {
context.handle(
_isCommunityMeta,
isCommunity.isAcceptableOrUnknown(
data['is_community']!,
_isCommunityMeta,
),
);
} else if (isInserting) {
context.missing(_isCommunityMeta);
}
if (data.containsKey('is_public')) {
context.handle(
_isPublicMeta,
isPublic.isAcceptableOrUnknown(data['is_public']!, _isPublicMeta),
);
} else if (isInserting) {
context.missing(_isPublicMeta);
}
if (data.containsKey('account_id')) {
context.handle(
_accountIdMeta,
@@ -184,6 +287,10 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
DriftSqlType.string,
data['${effectivePrefix}id'],
)!,
slug: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}slug'],
)!,
name: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}name'],
@@ -192,6 +299,22 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
DriftSqlType.string,
data['${effectivePrefix}description'],
),
verifiedAs: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}verified_as'],
),
verifiedAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}verified_at'],
),
isCommunity: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}is_community'],
)!,
isPublic: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}is_public'],
)!,
picture: $RealmsTable.$converterpicturen.fromSql(
attachedDatabase.typeMapping.read(
DriftSqlType.string,
@@ -240,8 +363,13 @@ class $RealmsTable extends Realms with TableInfo<$RealmsTable, Realm> {
class Realm extends DataClass implements Insertable<Realm> {
final String id;
final String slug;
final String? name;
final String? description;
final String? verifiedAs;
final DateTime? verifiedAt;
final bool isCommunity;
final bool isPublic;
final Map<String, dynamic>? picture;
final Map<String, dynamic>? background;
final String? accountId;
@@ -250,8 +378,13 @@ class Realm extends DataClass implements Insertable<Realm> {
final DateTime? deletedAt;
const Realm({
required this.id,
required this.slug,
this.name,
this.description,
this.verifiedAs,
this.verifiedAt,
required this.isCommunity,
required this.isPublic,
this.picture,
this.background,
this.accountId,
@@ -263,12 +396,21 @@ class Realm extends DataClass implements Insertable<Realm> {
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<String>(id);
map['slug'] = Variable<String>(slug);
if (!nullToAbsent || name != null) {
map['name'] = Variable<String>(name);
}
if (!nullToAbsent || description != null) {
map['description'] = Variable<String>(description);
}
if (!nullToAbsent || verifiedAs != null) {
map['verified_as'] = Variable<String>(verifiedAs);
}
if (!nullToAbsent || verifiedAt != null) {
map['verified_at'] = Variable<DateTime>(verifiedAt);
}
map['is_community'] = Variable<bool>(isCommunity);
map['is_public'] = Variable<bool>(isPublic);
if (!nullToAbsent || picture != null) {
map['picture'] = Variable<String>(
$RealmsTable.$converterpicturen.toSql(picture),
@@ -293,10 +435,19 @@ class Realm extends DataClass implements Insertable<Realm> {
RealmsCompanion toCompanion(bool nullToAbsent) {
return RealmsCompanion(
id: Value(id),
slug: Value(slug),
name: name == null && nullToAbsent ? const Value.absent() : Value(name),
description: description == null && nullToAbsent
? const Value.absent()
: Value(description),
verifiedAs: verifiedAs == null && nullToAbsent
? const Value.absent()
: Value(verifiedAs),
verifiedAt: verifiedAt == null && nullToAbsent
? const Value.absent()
: Value(verifiedAt),
isCommunity: Value(isCommunity),
isPublic: Value(isPublic),
picture: picture == null && nullToAbsent
? const Value.absent()
: Value(picture),
@@ -321,8 +472,13 @@ class Realm extends DataClass implements Insertable<Realm> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return Realm(
id: serializer.fromJson<String>(json['id']),
slug: serializer.fromJson<String>(json['slug']),
name: serializer.fromJson<String?>(json['name']),
description: serializer.fromJson<String?>(json['description']),
verifiedAs: serializer.fromJson<String?>(json['verifiedAs']),
verifiedAt: serializer.fromJson<DateTime?>(json['verifiedAt']),
isCommunity: serializer.fromJson<bool>(json['isCommunity']),
isPublic: serializer.fromJson<bool>(json['isPublic']),
picture: serializer.fromJson<Map<String, dynamic>?>(json['picture']),
background: serializer.fromJson<Map<String, dynamic>?>(
json['background'],
@@ -338,8 +494,13 @@ class Realm extends DataClass implements Insertable<Realm> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'slug': serializer.toJson<String>(slug),
'name': serializer.toJson<String?>(name),
'description': serializer.toJson<String?>(description),
'verifiedAs': serializer.toJson<String?>(verifiedAs),
'verifiedAt': serializer.toJson<DateTime?>(verifiedAt),
'isCommunity': serializer.toJson<bool>(isCommunity),
'isPublic': serializer.toJson<bool>(isPublic),
'picture': serializer.toJson<Map<String, dynamic>?>(picture),
'background': serializer.toJson<Map<String, dynamic>?>(background),
'accountId': serializer.toJson<String?>(accountId),
@@ -351,8 +512,13 @@ class Realm extends DataClass implements Insertable<Realm> {
Realm copyWith({
String? id,
String? slug,
Value<String?> name = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<String?> verifiedAs = const Value.absent(),
Value<DateTime?> verifiedAt = const Value.absent(),
bool? isCommunity,
bool? isPublic,
Value<Map<String, dynamic>?> picture = const Value.absent(),
Value<Map<String, dynamic>?> background = const Value.absent(),
Value<String?> accountId = const Value.absent(),
@@ -361,8 +527,13 @@ class Realm extends DataClass implements Insertable<Realm> {
Value<DateTime?> deletedAt = const Value.absent(),
}) => Realm(
id: id ?? this.id,
slug: slug ?? this.slug,
name: name.present ? name.value : this.name,
description: description.present ? description.value : this.description,
verifiedAs: verifiedAs.present ? verifiedAs.value : this.verifiedAs,
verifiedAt: verifiedAt.present ? verifiedAt.value : this.verifiedAt,
isCommunity: isCommunity ?? this.isCommunity,
isPublic: isPublic ?? this.isPublic,
picture: picture.present ? picture.value : this.picture,
background: background.present ? background.value : this.background,
accountId: accountId.present ? accountId.value : this.accountId,
@@ -373,10 +544,21 @@ class Realm extends DataClass implements Insertable<Realm> {
Realm copyWithCompanion(RealmsCompanion data) {
return Realm(
id: data.id.present ? data.id.value : this.id,
slug: data.slug.present ? data.slug.value : this.slug,
name: data.name.present ? data.name.value : this.name,
description: data.description.present
? data.description.value
: this.description,
verifiedAs: data.verifiedAs.present
? data.verifiedAs.value
: this.verifiedAs,
verifiedAt: data.verifiedAt.present
? data.verifiedAt.value
: this.verifiedAt,
isCommunity: data.isCommunity.present
? data.isCommunity.value
: this.isCommunity,
isPublic: data.isPublic.present ? data.isPublic.value : this.isPublic,
picture: data.picture.present ? data.picture.value : this.picture,
background: data.background.present
? data.background.value
@@ -392,8 +574,13 @@ class Realm extends DataClass implements Insertable<Realm> {
String toString() {
return (StringBuffer('Realm(')
..write('id: $id, ')
..write('slug: $slug, ')
..write('name: $name, ')
..write('description: $description, ')
..write('verifiedAs: $verifiedAs, ')
..write('verifiedAt: $verifiedAt, ')
..write('isCommunity: $isCommunity, ')
..write('isPublic: $isPublic, ')
..write('picture: $picture, ')
..write('background: $background, ')
..write('accountId: $accountId, ')
@@ -407,8 +594,13 @@ class Realm extends DataClass implements Insertable<Realm> {
@override
int get hashCode => Object.hash(
id,
slug,
name,
description,
verifiedAs,
verifiedAt,
isCommunity,
isPublic,
picture,
background,
accountId,
@@ -421,8 +613,13 @@ class Realm extends DataClass implements Insertable<Realm> {
identical(this, other) ||
(other is Realm &&
other.id == this.id &&
other.slug == this.slug &&
other.name == this.name &&
other.description == this.description &&
other.verifiedAs == this.verifiedAs &&
other.verifiedAt == this.verifiedAt &&
other.isCommunity == this.isCommunity &&
other.isPublic == this.isPublic &&
other.picture == this.picture &&
other.background == this.background &&
other.accountId == this.accountId &&
@@ -433,8 +630,13 @@ class Realm extends DataClass implements Insertable<Realm> {
class RealmsCompanion extends UpdateCompanion<Realm> {
final Value<String> id;
final Value<String> slug;
final Value<String?> name;
final Value<String?> description;
final Value<String?> verifiedAs;
final Value<DateTime?> verifiedAt;
final Value<bool> isCommunity;
final Value<bool> isPublic;
final Value<Map<String, dynamic>?> picture;
final Value<Map<String, dynamic>?> background;
final Value<String?> accountId;
@@ -444,8 +646,13 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
final Value<int> rowid;
const RealmsCompanion({
this.id = const Value.absent(),
this.slug = const Value.absent(),
this.name = const Value.absent(),
this.description = const Value.absent(),
this.verifiedAs = const Value.absent(),
this.verifiedAt = const Value.absent(),
this.isCommunity = const Value.absent(),
this.isPublic = const Value.absent(),
this.picture = const Value.absent(),
this.background = const Value.absent(),
this.accountId = const Value.absent(),
@@ -456,8 +663,13 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
});
RealmsCompanion.insert({
required String id,
required String slug,
this.name = const Value.absent(),
this.description = const Value.absent(),
this.verifiedAs = const Value.absent(),
this.verifiedAt = const Value.absent(),
required bool isCommunity,
required bool isPublic,
this.picture = const Value.absent(),
this.background = const Value.absent(),
this.accountId = const Value.absent(),
@@ -466,12 +678,20 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
this.deletedAt = const Value.absent(),
this.rowid = const Value.absent(),
}) : id = Value(id),
slug = Value(slug),
isCommunity = Value(isCommunity),
isPublic = Value(isPublic),
createdAt = Value(createdAt),
updatedAt = Value(updatedAt);
static Insertable<Realm> custom({
Expression<String>? id,
Expression<String>? slug,
Expression<String>? name,
Expression<String>? description,
Expression<String>? verifiedAs,
Expression<DateTime>? verifiedAt,
Expression<bool>? isCommunity,
Expression<bool>? isPublic,
Expression<String>? picture,
Expression<String>? background,
Expression<String>? accountId,
@@ -482,8 +702,13 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (slug != null) 'slug': slug,
if (name != null) 'name': name,
if (description != null) 'description': description,
if (verifiedAs != null) 'verified_as': verifiedAs,
if (verifiedAt != null) 'verified_at': verifiedAt,
if (isCommunity != null) 'is_community': isCommunity,
if (isPublic != null) 'is_public': isPublic,
if (picture != null) 'picture': picture,
if (background != null) 'background': background,
if (accountId != null) 'account_id': accountId,
@@ -496,8 +721,13 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
RealmsCompanion copyWith({
Value<String>? id,
Value<String>? slug,
Value<String?>? name,
Value<String?>? description,
Value<String?>? verifiedAs,
Value<DateTime?>? verifiedAt,
Value<bool>? isCommunity,
Value<bool>? isPublic,
Value<Map<String, dynamic>?>? picture,
Value<Map<String, dynamic>?>? background,
Value<String?>? accountId,
@@ -508,8 +738,13 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
}) {
return RealmsCompanion(
id: id ?? this.id,
slug: slug ?? this.slug,
name: name ?? this.name,
description: description ?? this.description,
verifiedAs: verifiedAs ?? this.verifiedAs,
verifiedAt: verifiedAt ?? this.verifiedAt,
isCommunity: isCommunity ?? this.isCommunity,
isPublic: isPublic ?? this.isPublic,
picture: picture ?? this.picture,
background: background ?? this.background,
accountId: accountId ?? this.accountId,
@@ -526,12 +761,27 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (slug.present) {
map['slug'] = Variable<String>(slug.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (description.present) {
map['description'] = Variable<String>(description.value);
}
if (verifiedAs.present) {
map['verified_as'] = Variable<String>(verifiedAs.value);
}
if (verifiedAt.present) {
map['verified_at'] = Variable<DateTime>(verifiedAt.value);
}
if (isCommunity.present) {
map['is_community'] = Variable<bool>(isCommunity.value);
}
if (isPublic.present) {
map['is_public'] = Variable<bool>(isPublic.value);
}
if (picture.present) {
map['picture'] = Variable<String>(
$RealmsTable.$converterpicturen.toSql(picture.value),
@@ -564,8 +814,13 @@ class RealmsCompanion extends UpdateCompanion<Realm> {
String toString() {
return (StringBuffer('RealmsCompanion(')
..write('id: $id, ')
..write('slug: $slug, ')
..write('name: $name, ')
..write('description: $description, ')
..write('verifiedAs: $verifiedAs, ')
..write('verifiedAt: $verifiedAt, ')
..write('isCommunity: $isCommunity, ')
..write('isPublic: $isPublic, ')
..write('picture: $picture, ')
..write('background: $background, ')
..write('accountId: $accountId, ')
@@ -695,6 +950,21 @@ class $ChatRoomsTable extends ChatRooms
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _isPinnedMeta = const VerificationMeta(
'isPinned',
);
@override
late final GeneratedColumn<bool> isPinned = GeneratedColumn<bool>(
'is_pinned',
aliasedName,
true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("is_pinned" IN (0, 1))',
),
defaultValue: const Constant(false),
);
static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt',
);
@@ -740,6 +1010,7 @@ class $ChatRoomsTable extends ChatRooms
background,
realmId,
accountId,
isPinned,
createdAt,
updatedAt,
deletedAt,
@@ -811,6 +1082,12 @@ class $ChatRoomsTable extends ChatRooms
accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta),
);
}
if (data.containsKey('is_pinned')) {
context.handle(
_isPinnedMeta,
isPinned.isAcceptableOrUnknown(data['is_pinned']!, _isPinnedMeta),
);
}
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
@@ -886,6 +1163,10 @@ class $ChatRoomsTable extends ChatRooms
DriftSqlType.string,
data['${effectivePrefix}account_id'],
),
isPinned: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}is_pinned'],
),
createdAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}created_at'],
@@ -927,6 +1208,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
final Map<String, dynamic>? background;
final String? realmId;
final String? accountId;
final bool? isPinned;
final DateTime createdAt;
final DateTime updatedAt;
final DateTime? deletedAt;
@@ -941,6 +1223,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
this.background,
this.realmId,
this.accountId,
this.isPinned,
required this.createdAt,
required this.updatedAt,
this.deletedAt,
@@ -978,6 +1261,9 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
if (!nullToAbsent || accountId != null) {
map['account_id'] = Variable<String>(accountId);
}
if (!nullToAbsent || isPinned != null) {
map['is_pinned'] = Variable<bool>(isPinned);
}
map['created_at'] = Variable<DateTime>(createdAt);
map['updated_at'] = Variable<DateTime>(updatedAt);
if (!nullToAbsent || deletedAt != null) {
@@ -1012,6 +1298,9 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
accountId: accountId == null && nullToAbsent
? const Value.absent()
: Value(accountId),
isPinned: isPinned == null && nullToAbsent
? const Value.absent()
: Value(isPinned),
createdAt: Value(createdAt),
updatedAt: Value(updatedAt),
deletedAt: deletedAt == null && nullToAbsent
@@ -1038,6 +1327,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
),
realmId: serializer.fromJson<String?>(json['realmId']),
accountId: serializer.fromJson<String?>(json['accountId']),
isPinned: serializer.fromJson<bool?>(json['isPinned']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
deletedAt: serializer.fromJson<DateTime?>(json['deletedAt']),
@@ -1057,6 +1347,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
'background': serializer.toJson<Map<String, dynamic>?>(background),
'realmId': serializer.toJson<String?>(realmId),
'accountId': serializer.toJson<String?>(accountId),
'isPinned': serializer.toJson<bool?>(isPinned),
'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'deletedAt': serializer.toJson<DateTime?>(deletedAt),
@@ -1074,6 +1365,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
Value<Map<String, dynamic>?> background = const Value.absent(),
Value<String?> realmId = const Value.absent(),
Value<String?> accountId = const Value.absent(),
Value<bool?> isPinned = const Value.absent(),
DateTime? createdAt,
DateTime? updatedAt,
Value<DateTime?> deletedAt = const Value.absent(),
@@ -1088,6 +1380,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
background: background.present ? background.value : this.background,
realmId: realmId.present ? realmId.value : this.realmId,
accountId: accountId.present ? accountId.value : this.accountId,
isPinned: isPinned.present ? isPinned.value : this.isPinned,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt,
@@ -1110,6 +1403,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
: this.background,
realmId: data.realmId.present ? data.realmId.value : this.realmId,
accountId: data.accountId.present ? data.accountId.value : this.accountId,
isPinned: data.isPinned.present ? data.isPinned.value : this.isPinned,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt,
@@ -1129,6 +1423,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
..write('background: $background, ')
..write('realmId: $realmId, ')
..write('accountId: $accountId, ')
..write('isPinned: $isPinned, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('deletedAt: $deletedAt')
@@ -1148,6 +1443,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
background,
realmId,
accountId,
isPinned,
createdAt,
updatedAt,
deletedAt,
@@ -1166,6 +1462,7 @@ class ChatRoom extends DataClass implements Insertable<ChatRoom> {
other.background == this.background &&
other.realmId == this.realmId &&
other.accountId == this.accountId &&
other.isPinned == this.isPinned &&
other.createdAt == this.createdAt &&
other.updatedAt == this.updatedAt &&
other.deletedAt == this.deletedAt);
@@ -1182,6 +1479,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
final Value<Map<String, dynamic>?> background;
final Value<String?> realmId;
final Value<String?> accountId;
final Value<bool?> isPinned;
final Value<DateTime> createdAt;
final Value<DateTime> updatedAt;
final Value<DateTime?> deletedAt;
@@ -1197,6 +1495,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
this.background = const Value.absent(),
this.realmId = const Value.absent(),
this.accountId = const Value.absent(),
this.isPinned = const Value.absent(),
this.createdAt = const Value.absent(),
this.updatedAt = const Value.absent(),
this.deletedAt = const Value.absent(),
@@ -1213,6 +1512,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
this.background = const Value.absent(),
this.realmId = const Value.absent(),
this.accountId = const Value.absent(),
this.isPinned = const Value.absent(),
required DateTime createdAt,
required DateTime updatedAt,
this.deletedAt = const Value.absent(),
@@ -1232,6 +1532,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
Expression<String>? background,
Expression<String>? realmId,
Expression<String>? accountId,
Expression<bool>? isPinned,
Expression<DateTime>? createdAt,
Expression<DateTime>? updatedAt,
Expression<DateTime>? deletedAt,
@@ -1248,6 +1549,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
if (background != null) 'background': background,
if (realmId != null) 'realm_id': realmId,
if (accountId != null) 'account_id': accountId,
if (isPinned != null) 'is_pinned': isPinned,
if (createdAt != null) 'created_at': createdAt,
if (updatedAt != null) 'updated_at': updatedAt,
if (deletedAt != null) 'deleted_at': deletedAt,
@@ -1266,6 +1568,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
Value<Map<String, dynamic>?>? background,
Value<String?>? realmId,
Value<String?>? accountId,
Value<bool?>? isPinned,
Value<DateTime>? createdAt,
Value<DateTime>? updatedAt,
Value<DateTime?>? deletedAt,
@@ -1282,6 +1585,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
background: background ?? this.background,
realmId: realmId ?? this.realmId,
accountId: accountId ?? this.accountId,
isPinned: isPinned ?? this.isPinned,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
deletedAt: deletedAt ?? this.deletedAt,
@@ -1326,6 +1630,9 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
if (accountId.present) {
map['account_id'] = Variable<String>(accountId.value);
}
if (isPinned.present) {
map['is_pinned'] = Variable<bool>(isPinned.value);
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
@@ -1354,6 +1661,7 @@ class ChatRoomsCompanion extends UpdateCompanion<ChatRoom> {
..write('background: $background, ')
..write('realmId: $realmId, ')
..write('accountId: $accountId, ')
..write('isPinned: $isPinned, ')
..write('createdAt: $createdAt, ')
..write('updatedAt: $updatedAt, ')
..write('deletedAt: $deletedAt, ')
@@ -3719,8 +4027,13 @@ abstract class _$AppDatabase extends GeneratedDatabase {
typedef $$RealmsTableCreateCompanionBuilder =
RealmsCompanion Function({
required String id,
required String slug,
Value<String?> name,
Value<String?> description,
Value<String?> verifiedAs,
Value<DateTime?> verifiedAt,
required bool isCommunity,
required bool isPublic,
Value<Map<String, dynamic>?> picture,
Value<Map<String, dynamic>?> background,
Value<String?> accountId,
@@ -3732,8 +4045,13 @@ typedef $$RealmsTableCreateCompanionBuilder =
typedef $$RealmsTableUpdateCompanionBuilder =
RealmsCompanion Function({
Value<String> id,
Value<String> slug,
Value<String?> name,
Value<String?> description,
Value<String?> verifiedAs,
Value<DateTime?> verifiedAt,
Value<bool> isCommunity,
Value<bool> isPublic,
Value<Map<String, dynamic>?> picture,
Value<Map<String, dynamic>?> background,
Value<String?> accountId,
@@ -3780,6 +4098,11 @@ class $$RealmsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get slug => $composableBuilder(
column: $table.slug,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get name => $composableBuilder(
column: $table.name,
builder: (column) => ColumnFilters(column),
@@ -3790,6 +4113,26 @@ class $$RealmsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get verifiedAs => $composableBuilder(
column: $table.verifiedAs,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get verifiedAt => $composableBuilder(
column: $table.verifiedAt,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get isCommunity => $composableBuilder(
column: $table.isCommunity,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get isPublic => $composableBuilder(
column: $table.isPublic,
builder: (column) => ColumnFilters(column),
);
ColumnWithTypeConverterFilters<
Map<String, dynamic>?,
Map<String, dynamic>,
@@ -3870,6 +4213,11 @@ class $$RealmsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get slug => $composableBuilder(
column: $table.slug,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get name => $composableBuilder(
column: $table.name,
builder: (column) => ColumnOrderings(column),
@@ -3880,6 +4228,26 @@ class $$RealmsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get verifiedAs => $composableBuilder(
column: $table.verifiedAs,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get verifiedAt => $composableBuilder(
column: $table.verifiedAt,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get isCommunity => $composableBuilder(
column: $table.isCommunity,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get isPublic => $composableBuilder(
column: $table.isPublic,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get picture => $composableBuilder(
column: $table.picture,
builder: (column) => ColumnOrderings(column),
@@ -3923,6 +4291,9 @@ class $$RealmsTableAnnotationComposer
GeneratedColumn<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get slug =>
$composableBuilder(column: $table.slug, builder: (column) => column);
GeneratedColumn<String> get name =>
$composableBuilder(column: $table.name, builder: (column) => column);
@@ -3931,6 +4302,24 @@ class $$RealmsTableAnnotationComposer
builder: (column) => column,
);
GeneratedColumn<String> get verifiedAs => $composableBuilder(
column: $table.verifiedAs,
builder: (column) => column,
);
GeneratedColumn<DateTime> get verifiedAt => $composableBuilder(
column: $table.verifiedAt,
builder: (column) => column,
);
GeneratedColumn<bool> get isCommunity => $composableBuilder(
column: $table.isCommunity,
builder: (column) => column,
);
GeneratedColumn<bool> get isPublic =>
$composableBuilder(column: $table.isPublic, builder: (column) => column);
GeneratedColumnWithTypeConverter<Map<String, dynamic>?, String> get picture =>
$composableBuilder(column: $table.picture, builder: (column) => column);
@@ -4007,8 +4396,13 @@ class $$RealmsTableTableManager
updateCompanionCallback:
({
Value<String> id = const Value.absent(),
Value<String> slug = const Value.absent(),
Value<String?> name = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<String?> verifiedAs = const Value.absent(),
Value<DateTime?> verifiedAt = const Value.absent(),
Value<bool> isCommunity = const Value.absent(),
Value<bool> isPublic = const Value.absent(),
Value<Map<String, dynamic>?> picture = const Value.absent(),
Value<Map<String, dynamic>?> background = const Value.absent(),
Value<String?> accountId = const Value.absent(),
@@ -4018,8 +4412,13 @@ class $$RealmsTableTableManager
Value<int> rowid = const Value.absent(),
}) => RealmsCompanion(
id: id,
slug: slug,
name: name,
description: description,
verifiedAs: verifiedAs,
verifiedAt: verifiedAt,
isCommunity: isCommunity,
isPublic: isPublic,
picture: picture,
background: background,
accountId: accountId,
@@ -4031,8 +4430,13 @@ class $$RealmsTableTableManager
createCompanionCallback:
({
required String id,
required String slug,
Value<String?> name = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<String?> verifiedAs = const Value.absent(),
Value<DateTime?> verifiedAt = const Value.absent(),
required bool isCommunity,
required bool isPublic,
Value<Map<String, dynamic>?> picture = const Value.absent(),
Value<Map<String, dynamic>?> background = const Value.absent(),
Value<String?> accountId = const Value.absent(),
@@ -4042,8 +4446,13 @@ class $$RealmsTableTableManager
Value<int> rowid = const Value.absent(),
}) => RealmsCompanion.insert(
id: id,
slug: slug,
name: name,
description: description,
verifiedAs: verifiedAs,
verifiedAt: verifiedAt,
isCommunity: isCommunity,
isPublic: isPublic,
picture: picture,
background: background,
accountId: accountId,
@@ -4110,6 +4519,7 @@ typedef $$ChatRoomsTableCreateCompanionBuilder =
Value<Map<String, dynamic>?> background,
Value<String?> realmId,
Value<String?> accountId,
Value<bool?> isPinned,
required DateTime createdAt,
required DateTime updatedAt,
Value<DateTime?> deletedAt,
@@ -4127,6 +4537,7 @@ typedef $$ChatRoomsTableUpdateCompanionBuilder =
Value<Map<String, dynamic>?> background,
Value<String?> realmId,
Value<String?> accountId,
Value<bool?> isPinned,
Value<DateTime> createdAt,
Value<DateTime> updatedAt,
Value<DateTime?> deletedAt,
@@ -4256,6 +4667,11 @@ class $$ChatRoomsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get isPinned => $composableBuilder(
column: $table.isPinned,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnFilters(column),
@@ -4399,6 +4815,11 @@ class $$ChatRoomsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get isPinned => $composableBuilder(
column: $table.isPinned,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnOrderings(column),
@@ -4481,6 +4902,9 @@ class $$ChatRoomsTableAnnotationComposer
GeneratedColumn<String> get accountId =>
$composableBuilder(column: $table.accountId, builder: (column) => column);
GeneratedColumn<bool> get isPinned =>
$composableBuilder(column: $table.isPinned, builder: (column) => column);
GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
@@ -4606,6 +5030,7 @@ class $$ChatRoomsTableTableManager
Value<Map<String, dynamic>?> background = const Value.absent(),
Value<String?> realmId = const Value.absent(),
Value<String?> accountId = const Value.absent(),
Value<bool?> isPinned = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
Value<DateTime> updatedAt = const Value.absent(),
Value<DateTime?> deletedAt = const Value.absent(),
@@ -4621,6 +5046,7 @@ class $$ChatRoomsTableTableManager
background: background,
realmId: realmId,
accountId: accountId,
isPinned: isPinned,
createdAt: createdAt,
updatedAt: updatedAt,
deletedAt: deletedAt,
@@ -4638,6 +5064,7 @@ class $$ChatRoomsTableTableManager
Value<Map<String, dynamic>?> background = const Value.absent(),
Value<String?> realmId = const Value.absent(),
Value<String?> accountId = const Value.absent(),
Value<bool?> isPinned = const Value.absent(),
required DateTime createdAt,
required DateTime updatedAt,
Value<DateTime?> deletedAt = const Value.absent(),
@@ -4653,6 +5080,7 @@ class $$ChatRoomsTableTableManager
background: background,
realmId: realmId,
accountId: accountId,
isPinned: isPinned,
createdAt: createdAt,
updatedAt: updatedAt,
deletedAt: deletedAt,

View File

@@ -38,8 +38,13 @@ class ListMapConverter
class Realms extends Table {
TextColumn get id => text()();
TextColumn get slug => text()();
TextColumn get name => text().nullable()();
TextColumn get description => text().nullable()();
TextColumn get verifiedAs => text().nullable()();
DateTimeColumn get verifiedAt => dateTime().nullable()();
BoolColumn get isCommunity => boolean()();
BoolColumn get isPublic => boolean()();
TextColumn get picture => text().map(const MapConverter()).nullable()();
TextColumn get background => text().map(const MapConverter()).nullable()();
TextColumn get accountId => text().nullable()();
@@ -64,6 +69,8 @@ class ChatRooms extends Table {
TextColumn get background => text().map(const MapConverter()).nullable()();
TextColumn get realmId => text().references(Realms, #id).nullable()();
TextColumn get accountId => text().nullable()();
BoolColumn get isPinned =>
boolean().nullable().withDefault(const Constant(false))();
DateTimeColumn get createdAt => dateTime()();
DateTimeColumn get updatedAt => dateTime()();
DateTimeColumn get deletedAt => dateTime().nullable()();

View File

@@ -24,6 +24,8 @@ sealed class SnChatRoom with _$SnChatRoom {
required DateTime updatedAt,
required DateTime? deletedAt,
required List<SnChatMember>? members,
// Frontend data
@Default(false) bool isPinned,
}) = _SnChatRoom;
factory SnChatRoom.fromJson(Map<String, dynamic> json) =>

View File

@@ -15,7 +15,8 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnChatRoom {
String get id; String? get name; String? get description; int get type; bool get isPublic; bool get isCommunity; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; String? get accountId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;
String get id; String? get name; String? get description; int get type; bool get isPublic; bool get isCommunity; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; String? get accountId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;// Frontend data
bool get isPinned;
/// Create a copy of SnChatRoom
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +29,16 @@ $SnChatRoomCopyWith<SnChatRoom> get copyWith => _$SnChatRoomCopyWithImpl<SnChatR
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members)&&(identical(other.isPinned, isPinned) || other.isPinned == isPinned));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members));
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members),isPinned);
@override
String toString() {
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members, isPinned: $isPinned)';
}
@@ -48,7 +49,7 @@ abstract mixin class $SnChatRoomCopyWith<$Res> {
factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl;
@useResult
$Res call({
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned
});
@@ -65,7 +66,7 @@ class _$SnChatRoomCopyWithImpl<$Res>
/// Create a copy of SnChatRoom
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,Object? isPinned = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
@@ -82,7 +83,8 @@ as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,members: freezed == members ? _self.members : members // ignore: cast_nullable_to_non_nullable
as List<SnChatMember>?,
as List<SnChatMember>?,isPinned: null == isPinned ? _self.isPinned : isPinned // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of SnChatRoom
@@ -200,10 +202,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnChatRoom() when $default != null:
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members);case _:
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members,_that.isPinned);case _:
return orElse();
}
@@ -221,10 +223,10 @@ return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned) $default,) {final _that = this;
switch (_that) {
case _SnChatRoom():
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members);}
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members,_that.isPinned);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -238,10 +240,10 @@ return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned)? $default,) {final _that = this;
switch (_that) {
case _SnChatRoom() when $default != null:
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members);case _:
return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,_that.isCommunity,_that.picture,_that.background,_that.realmId,_that.accountId,_that.realm,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.members,_that.isPinned);case _:
return null;
}
@@ -253,7 +255,7 @@ return $default(_that.id,_that.name,_that.description,_that.type,_that.isPublic,
@JsonSerializable()
class _SnChatRoom implements SnChatRoom {
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, this.isPublic = false, this.isCommunity = false, required this.picture, required this.background, required this.realmId, required this.accountId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members}): _members = members;
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, this.isPublic = false, this.isCommunity = false, required this.picture, required this.background, required this.realmId, required this.accountId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members, this.isPinned = false}): _members = members;
factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json);
@override final String id;
@@ -279,6 +281,8 @@ class _SnChatRoom implements SnChatRoom {
return EqualUnmodifiableListView(value);
}
// Frontend data
@override@JsonKey() final bool isPinned;
/// Create a copy of SnChatRoom
/// with the given fields replaced by the non-null parameter values.
@@ -293,16 +297,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members)&&(identical(other.isPinned, isPinned) || other.isPinned == isPinned));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members));
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,accountId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members),isPinned);
@override
String toString() {
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, accountId: $accountId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members, isPinned: $isPinned)';
}
@@ -313,7 +317,7 @@ abstract mixin class _$SnChatRoomCopyWith<$Res> implements $SnChatRoomCopyWith<$
factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl;
@override @useResult
$Res call({
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, String? accountId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members, bool isPinned
});
@@ -330,7 +334,7 @@ class __$SnChatRoomCopyWithImpl<$Res>
/// Create a copy of SnChatRoom
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? accountId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,Object? isPinned = null,}) {
return _then(_SnChatRoom(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
@@ -347,7 +351,8 @@ as SnRealm?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,members: freezed == members ? _self._members : members // ignore: cast_nullable_to_non_nullable
as List<SnChatMember>?,
as List<SnChatMember>?,isPinned: null == isPinned ? _self.isPinned : isPinned // ignore: cast_nullable_to_non_nullable
as bool,
));
}

View File

@@ -32,6 +32,7 @@ _SnChatRoom _$SnChatRoomFromJson(Map<String, dynamic> json) => _SnChatRoom(
members: (json['members'] as List<dynamic>?)
?.map((e) => SnChatMember.fromJson(e as Map<String, dynamic>))
.toList(),
isPinned: json['is_pinned'] as bool? ?? false,
);
Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) =>
@@ -51,6 +52,7 @@ Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) =>
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'members': instance.members?.map((e) => e.toJson()).toList(),
'is_pinned': instance.isPinned,
};
_SnChatMessage _$SnChatMessageFromJson(Map<String, dynamic> json) =>

View File

@@ -47,6 +47,7 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
try {
final localRoomsData = await db.select(db.chatRooms).get();
final localRealmsData = await db.select(db.realms).get();
if (localRoomsData.isNotEmpty) {
final localRooms = await Future.wait(
localRoomsData.map((row) async {
@@ -87,11 +88,15 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
: null,
realmId: row.realmId,
accountId: row.accountId,
realm: null,
realm: localRealmsData
.where((e) => e.id == row.realmId)
.map((e) => _buildRealmFromTableEntry(e))
.firstOrNull,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
members: members,
isPinned: row.isPinned ?? false,
);
}),
);
@@ -126,6 +131,29 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
return rooms;
}
SnRealm _buildRealmFromTableEntry(Realm localRealm) {
return SnRealm(
id: localRealm.id,
slug: localRealm.slug,
name: localRealm.name ?? localRealm.slug,
description: localRealm.description ?? '',
verifiedAs: localRealm.verifiedAs,
verifiedAt: localRealm.verifiedAt,
isCommunity: localRealm.isCommunity,
isPublic: localRealm.isPublic,
picture: localRealm.picture != null
? SnCloudFile.fromJson(localRealm.picture!)
: null,
background: localRealm.background != null
? SnCloudFile.fromJson(localRealm.background!)
: null,
accountId: localRealm.accountId ?? '',
createdAt: localRealm.createdAt,
updatedAt: localRealm.updatedAt,
deletedAt: localRealm.deletedAt,
);
}
Future<List<SnChatRoom>> _buildRoomsFromDb(AppDatabase db) async {
final localRoomsData = await db.select(db.chatRooms).get();
return Future.wait(
@@ -207,6 +235,7 @@ class ChatRoomJoinedNotifier extends _$ChatRoomJoinedNotifier {
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
members: members,
isPinned: row.isPinned ?? false,
);
}),
);

View File

@@ -34,7 +34,7 @@ final class ChatRoomJoinedNotifierProvider
}
String _$chatRoomJoinedNotifierHash() =>
r'65961aac28b5188900c4b25308f6fd080a14d5ab';
r'805b38e477df574c92b1ef3cd54527cfd03a55cb';
abstract class _$ChatRoomJoinedNotifier
extends $AsyncNotifier<List<SnChatRoom>> {

View File

@@ -20,8 +20,8 @@ import 'package:island/widgets/navigation/fab_menu.dart';
import 'package:island/widgets/response.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:island/pods/chat/chat_room.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class ChatListBodyWidget extends HookConsumerWidget {
final bool isFloating;
@@ -55,50 +55,95 @@ class ChatListBodyWidget extends HookConsumerWidget {
),
Expanded(
child: chats.when(
data: (items) => RefreshIndicator(
onRefresh: () => Future.sync(() {
ref.invalidate(chatRoomJoinedProvider);
}),
child: SuperListView.builder(
padding: EdgeInsets.only(bottom: 96),
itemCount: items
.where(
(item) =>
selectedTab.value == 0 ||
(selectedTab.value == 1 && item.type == 1) ||
(selectedTab.value == 2 && item.type != 1),
)
.length,
itemBuilder: (context, index) {
final filteredItems = items
.where(
(item) =>
selectedTab.value == 0 ||
(selectedTab.value == 1 && item.type == 1) ||
(selectedTab.value == 2 && item.type != 1),
)
.toList();
final item = filteredItems[index];
return ChatRoomListTile(
room: item,
isDirect: item.type == 1,
onTap: () {
if (isWideScreen(context)) {
context.replaceNamed(
'chatRoom',
pathParameters: {'id': item.id},
);
} else {
context.pushNamed(
'chatRoom',
pathParameters: {'id': item.id},
);
}
},
);
},
),
),
data: (items) {
final filteredItems = items.where(
(item) =>
selectedTab.value == 0 ||
(selectedTab.value == 1 && item.type == 1) ||
(selectedTab.value == 2 && item.type != 1),
);
final pinnedItems = filteredItems
.where((item) => item.isPinned)
.toList();
final unpinnedItems = filteredItems
.where((item) => !item.isPinned)
.toList();
return RefreshIndicator(
onRefresh: () => Future.sync(() {
ref.invalidate(chatRoomJoinedProvider);
}),
child: Column(
children: [
ExpansionTile(
backgroundColor: Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.5),
collapsedBackgroundColor: Theme.of(
context,
).colorScheme.surfaceContainer.withOpacity(0.5),
title: Text('pinnedChatRoom'.tr()),
leading: const Icon(Symbols.keep, fill: 1),
tilePadding: const EdgeInsets.symmetric(horizontal: 24),
initiallyExpanded: true,
children: [
for (final item in pinnedItems)
ChatRoomListTile(
room: item,
isDirect: item.type == 1,
onTap: () {
if (isWideScreen(context)) {
context.replaceNamed(
'chatRoom',
pathParameters: {'id': item.id},
);
} else {
context.pushNamed(
'chatRoom',
pathParameters: {'id': item.id},
);
}
},
),
],
),
Expanded(
child: SuperListView.builder(
padding: EdgeInsets.only(bottom: 96),
itemCount: unpinnedItems
.where(
(item) =>
selectedTab.value == 0 ||
(selectedTab.value == 1 && item.type == 1) ||
(selectedTab.value == 2 && item.type != 1),
)
.length,
itemBuilder: (context, index) {
final item = unpinnedItems[index];
return ChatRoomListTile(
room: item,
isDirect: item.type == 1,
onTap: () {
if (isWideScreen(context)) {
context.replaceNamed(
'chatRoom',
pathParameters: {'id': item.id},
);
} else {
context.pushNamed(
'chatRoom',
pathParameters: {'id': item.id},
);
}
},
);
},
),
),
],
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => ResponseErrorWidget(
error: error,

View File

@@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:gap/gap.dart';
@@ -42,6 +43,20 @@ class ChatDetailScreen extends HookConsumerWidget {
final roomIdentity = ref.watch(chatRoomIdentityProvider(id));
final totalMessages = ref.watch(totalMessagesCountProvider(id));
// Local state for pinned status to provide immediate UI feedback
final isPinned = useState<bool?>(null);
// Initialize pinned state from database
useEffect(() {
final db = ref.read(databaseProvider);
(db.select(
db.chatRooms,
)..where((r) => r.id.equals(id))).getSingleOrNull().then((room) {
isPinned.value = room?.isPinned ?? false;
});
return null;
}, [id]);
const kNotifyLevelText = [
'chatNotifyLevelAll',
'chatNotifyLevelMention',
@@ -83,46 +98,45 @@ class ChatDetailScreen extends HookConsumerWidget {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => SheetScaffold(
height: 320,
titleText: 'chatNotifyLevel'.tr(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text('chatNotifyLevelAll').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.notifications_active),
selected: identity.notify == 0,
onTap: () {
setNotifyLevel(0);
Navigator.pop(context);
},
),
ListTile(
title: const Text('chatNotifyLevelMention').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.alternate_email),
selected: identity.notify == 1,
onTap: () {
setNotifyLevel(1);
Navigator.pop(context);
},
),
ListTile(
title: const Text('chatNotifyLevelNone').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.notifications_off),
selected: identity.notify == 2,
onTap: () {
setNotifyLevel(2);
Navigator.pop(context);
},
),
],
builder: (context) => SheetScaffold(
height: 320,
titleText: 'chatNotifyLevel'.tr(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text('chatNotifyLevelAll').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.notifications_active),
selected: identity.notify == 0,
onTap: () {
setNotifyLevel(0);
Navigator.pop(context);
},
),
),
ListTile(
title: const Text('chatNotifyLevelMention').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.alternate_email),
selected: identity.notify == 1,
onTap: () {
setNotifyLevel(1);
Navigator.pop(context);
},
),
ListTile(
title: const Text('chatNotifyLevelNone').tr(),
subtitle: const Text('chatNotifyLevelDescription').tr(),
leading: const Icon(Icons.notifications_off),
selected: identity.notify == 2,
onTap: () {
setNotifyLevel(2);
Navigator.pop(context);
},
),
],
),
),
);
}
@@ -132,118 +146,117 @@ class ChatDetailScreen extends HookConsumerWidget {
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: const Text('chatBreak').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('chatBreakDescription').tr(),
const Gap(16),
ListTile(
title: const Text('chatBreakClearButton').tr(),
subtitle: const Text('chatBreakClear').tr(),
leading: const Icon(Icons.notifications_active),
onTap: () {
setChatBreak(now);
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakCleared'.tr());
}
},
),
ListTile(
title: const Text('chatBreak5m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak5m'.tr()]),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 5)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['5m']));
}
},
),
ListTile(
title: const Text('chatBreak10m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak10m'.tr()]),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 10)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['10m']));
}
},
),
ListTile(
title: const Text('chatBreak15m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak15m'.tr()]),
leading: const Icon(Symbols.timer_3),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 15)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['15m']));
}
},
),
ListTile(
title: const Text('chatBreak30m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak30m'.tr()]),
leading: const Icon(Symbols.timer),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 30)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['30m']));
}
},
),
const Gap(8),
TextField(
controller: durationController,
decoration: InputDecoration(
labelText: 'chatBreakCustomMinutes'.tr(),
hintText: 'chatBreakEnterMinutes'.tr(),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.check),
onPressed: () {
final minutes = int.tryParse(durationController.text);
if (minutes != null && minutes > 0) {
setChatBreak(now.add(Duration(minutes: minutes)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar(
'chatBreakSet'.tr(args: ['${minutes}m']),
);
}
}
},
),
),
keyboardType: TextInputType.number,
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
),
],
builder: (context) => AlertDialog(
title: const Text('chatBreak').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('chatBreakDescription').tr(),
const Gap(16),
ListTile(
title: const Text('chatBreakClearButton').tr(),
subtitle: const Text('chatBreakClear').tr(),
leading: const Icon(Icons.notifications_active),
onTap: () {
setChatBreak(now);
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakCleared'.tr());
}
},
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('cancel').tr(),
ListTile(
title: const Text('chatBreak5m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak5m'.tr()]),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 5)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['5m']));
}
},
),
ListTile(
title: const Text('chatBreak10m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak10m'.tr()]),
leading: const Icon(Symbols.circle),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 10)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['10m']));
}
},
),
ListTile(
title: const Text('chatBreak15m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak15m'.tr()]),
leading: const Icon(Symbols.timer_3),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 15)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['15m']));
}
},
),
ListTile(
title: const Text('chatBreak30m').tr(),
subtitle: const Text(
'chatBreakHour',
).tr(args: ['chatBreak30m'.tr()]),
leading: const Icon(Symbols.timer),
onTap: () {
setChatBreak(now.add(const Duration(minutes: 30)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar('chatBreakSet'.tr(args: ['30m']));
}
},
),
const Gap(8),
TextField(
controller: durationController,
decoration: InputDecoration(
labelText: 'chatBreakCustomMinutes'.tr(),
hintText: 'chatBreakEnterMinutes'.tr(),
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.check),
onPressed: () {
final minutes = int.tryParse(durationController.text);
if (minutes != null && minutes > 0) {
setChatBreak(now.add(Duration(minutes: minutes)));
Navigator.pop(context);
if (context.mounted) {
showSnackBar(
'chatBreakSet'.tr(args: ['${minutes}m']),
);
}
}
},
),
),
],
keyboardType: TextInputType.number,
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('cancel').tr(),
),
],
),
);
}
@@ -256,175 +269,197 @@ class ChatDetailScreen extends HookConsumerWidget {
return AppScaffold(
body: roomState.when(
loading: () => const Center(child: CircularProgressIndicator()),
error:
(error, _) => Center(
child: Text('errorGeneric'.tr(args: [error.toString()])),
),
data:
(currentRoom) => CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 180,
pinned: true,
leading: PageBackButton(shadows: [iconShadow]),
flexibleSpace: FlexibleSpaceBar(
background:
(currentRoom!.type == 1 &&
currentRoom.background?.id != null)
? CloudImageWidget(
fileId: currentRoom.background!.id,
)
: (currentRoom.type == 1 &&
currentRoom.members!.length == 1 &&
currentRoom
.members!
.first
.account
.profile
.background
?.id !=
null)
? CloudImageWidget(
fileId:
currentRoom
.members!
.first
.account
.profile
.background!
.id,
)
: currentRoom.background?.id != null
? CloudImageWidget(
fileId: currentRoom.background!.id,
fit: BoxFit.cover,
)
: Container(
color:
Theme.of(context).appBarTheme.backgroundColor,
),
title: Text(
(currentRoom.type == 1 && currentRoom.name == null)
? currentRoom.members!
.map((e) => e.account.nick)
.join(', ')
: currentRoom.name!,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor,
shadows: [iconShadow],
error: (error, _) =>
Center(child: Text('errorGeneric'.tr(args: [error.toString()]))),
data: (currentRoom) => CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 180,
pinned: true,
leading: PageBackButton(shadows: [iconShadow]),
flexibleSpace: FlexibleSpaceBar(
background:
(currentRoom!.type == 1 &&
currentRoom.background?.id != null)
? CloudImageWidget(fileId: currentRoom.background!.id)
: (currentRoom.type == 1 &&
currentRoom.members!.length == 1 &&
currentRoom
.members!
.first
.account
.profile
.background
?.id !=
null)
? CloudImageWidget(
fileId: currentRoom
.members!
.first
.account
.profile
.background!
.id,
)
: currentRoom.background?.id != null
? CloudImageWidget(
fileId: currentRoom.background!.id,
fit: BoxFit.cover,
)
: Container(
color: Theme.of(context).appBarTheme.backgroundColor,
),
),
title: Text(
(currentRoom.type == 1 && currentRoom.name == null)
? currentRoom.members!
.map((e) => e.account.nick)
.join(', ')
: currentRoom.name!,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor,
shadows: [iconShadow],
),
actions: [
IconButton(
icon: const Icon(Icons.people, shadows: [iconShadow]),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder:
(context) => _ChatMemberListSheet(roomId: id),
);
},
),
_ChatRoomActionMenu(id: id, iconShadow: iconShadow),
const Gap(8),
],
),
SliverToBoxAdapter(
child: Column(
),
actions: [
IconButton(
icon: const Icon(Icons.people, shadows: [iconShadow]),
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) => _ChatMemberListSheet(roomId: id),
);
},
),
_ChatRoomActionMenu(id: id, iconShadow: iconShadow),
const Gap(8),
],
),
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentRoom.description ?? 'descriptionNone'.tr(),
style: const TextStyle(fontSize: 16),
).padding(all: 24),
const Divider(height: 1),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentRoom.description ?? 'descriptionNone'.tr(),
style: const TextStyle(fontSize: 16),
).padding(all: 24),
const Divider(height: 1),
// Pin/Unpin Switch
if (isPinned.value != null)
SwitchListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 24),
secondary: Icon(
Symbols.push_pin,
color: Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
title: const Text('pinChatRoom').tr(),
subtitle: const Text('pinChatRoomDescription').tr(),
value: isPinned.value!,
onChanged: (value) async {
// Update local state immediately for instant UI feedback
isPinned.value = value;
final db = ref.read(databaseProvider);
await db.toggleChatRoomPinned(id);
// Re-verify the state from database in case of error
final room = await (db.select(
db.chatRooms,
)..where((r) => r.id.equals(id))).getSingleOrNull();
final actualPinned = room?.isPinned ?? false;
if (actualPinned != value) {
// Revert if database operation failed
isPinned.value = actualPinned;
}
showSnackBar(
value
? 'chatRoomPinned'.tr()
: 'chatRoomUnpinned'.tr(),
);
},
),
roomIdentity.when(
data:
(identity) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Symbols.notifications),
trailing: const Icon(Symbols.chevron_right),
title: const Text('chatNotifyLevel').tr(),
subtitle: Text(
kNotifyLevelText[identity!.notify].tr(),
),
onTap:
() =>
showNotifyLevelBottomSheet(identity),
),
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Icons.timer),
trailing: const Icon(Symbols.chevron_right),
title: const Text('chatBreak').tr(),
subtitle:
identity.breakUntil != null &&
identity.breakUntil!.isAfter(
DateTime.now(),
)
? Text(
DateFormat(
'yyyy-MM-dd HH:mm',
).format(identity.breakUntil!),
)
: const Text('chatBreakNone').tr(),
onTap: () => showChatBreakDialog(),
),
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Icons.search),
trailing: const Icon(Symbols.chevron_right),
title: const Text('searchMessages').tr(),
subtitle: totalMessages.when(
data:
(count) => Text(
'messagesCount'.tr(
args: [count.toString()],
),
),
loading:
() => const CircularProgressIndicator(),
error:
(err, stack) => Text(
'errorGeneric'.tr(
args: [err.toString()],
),
),
),
onTap: () async {
final result = await context.pushNamed(
'searchMessages',
pathParameters: {'id': id},
);
if (result is SearchMessagesResult) {
// Navigate back to room screen with message to jump to
if (context.mounted) {
context.pop(result);
}
}
},
),
],
data: (identity) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Symbols.notifications),
trailing: const Icon(Symbols.chevron_right),
title: const Text('chatNotifyLevel').tr(),
subtitle: Text(
kNotifyLevelText[identity!.notify].tr(),
),
onTap: () => showNotifyLevelBottomSheet(identity),
),
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Icons.timer),
trailing: const Icon(Symbols.chevron_right),
title: const Text('chatBreak').tr(),
subtitle:
identity.breakUntil != null &&
identity.breakUntil!.isAfter(
DateTime.now(),
)
? Text(
DateFormat(
'yyyy-MM-dd HH:mm',
).format(identity.breakUntil!),
)
: const Text('chatBreakNone').tr(),
onTap: () => showChatBreakDialog(),
),
ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 24,
),
leading: const Icon(Icons.search),
trailing: const Icon(Symbols.chevron_right),
title: const Text('searchMessages').tr(),
subtitle: totalMessages.when(
data: (count) => Text(
'messagesCount'.tr(args: [count.toString()]),
),
loading: () =>
const CircularProgressIndicator(),
error: (err, stack) => Text(
'errorGeneric'.tr(args: [err.toString()]),
),
),
onTap: () async {
final result = await context.pushNamed(
'searchMessages',
pathParameters: {'id': id},
);
if (result is SearchMessagesResult) {
// Navigate back to room screen with message to jump to
if (context.mounted) {
context.pop(result);
}
}
},
),
],
),
error: (_, _) => const SizedBox.shrink(),
loading: () => const SizedBox.shrink(),
),
],
),
),
],
],
),
),
],
),
),
);
}
@@ -447,97 +482,94 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
return PopupMenuButton(
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
itemBuilder:
(context) => [
if (isManagable)
PopupMenuItem(
onTap: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => EditChatScreen(id: id),
).then((value) {
if (value != null) {
// Invalidate to refresh room data after edit
ref.invalidate(chatMemberListProvider(id));
}
});
},
child: Row(
children: [
Icon(
Icons.edit,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
const Gap(12),
const Text('editChatRoom').tr(),
],
itemBuilder: (context) => [
if (isManagable)
PopupMenuItem(
onTap: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) => EditChatScreen(id: id),
).then((value) {
if (value != null) {
// Invalidate to refresh room data after edit
ref.invalidate(chatMemberListProvider(id));
}
});
},
child: Row(
children: [
Icon(
Icons.edit,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
if (isManagable)
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.delete, color: Colors.red),
const Gap(12),
const Text(
'deleteChatRoom',
style: TextStyle(color: Colors.red),
).tr(),
],
const Gap(12),
const Text('editChatRoom').tr(),
],
),
),
if (isManagable)
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.delete, color: Colors.red),
const Gap(12),
const Text(
'deleteChatRoom',
style: TextStyle(color: Colors.red),
).tr(),
],
),
onTap: () {
showConfirmAlert(
'deleteChatRoomHint'.tr(),
'deleteChatRoom'.tr(),
isDanger: true,
).then((confirm) async {
if (confirm) {
final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id');
ref.invalidate(chatRoomJoinedProvider);
if (context.mounted) {
context.pop();
}
}
});
},
)
else
PopupMenuItem(
child: Row(
children: [
Icon(
Icons.exit_to_app,
color: Theme.of(context).colorScheme.error,
),
onTap: () {
showConfirmAlert(
'deleteChatRoomHint'.tr(),
'deleteChatRoom'.tr(),
isDanger: true,
).then((confirm) async {
if (confirm) {
final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id');
ref.invalidate(chatRoomJoinedProvider);
if (context.mounted) {
context.pop();
}
}
});
},
)
else
PopupMenuItem(
child: Row(
children: [
Icon(
Icons.exit_to_app,
color: Theme.of(context).colorScheme.error,
),
const Gap(12),
Text(
'leaveChatRoom',
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
).tr(),
],
),
onTap: () {
showConfirmAlert(
'leaveChatRoomHint'.tr(),
'leaveChatRoom'.tr(),
).then((confirm) async {
if (confirm) {
final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id/members/me');
ref.invalidate(chatRoomJoinedProvider);
if (context.mounted) {
context.pop();
}
}
});
},
),
],
const Gap(12),
Text(
'leaveChatRoom',
style: TextStyle(color: Theme.of(context).colorScheme.error),
).tr(),
],
),
onTap: () {
showConfirmAlert(
'leaveChatRoomHint'.tr(),
'leaveChatRoom'.tr(),
).then((confirm) async {
if (confirm) {
final client = ref.watch(apiClientProvider);
await client.delete('/sphere/chat/$id/members/me');
ref.invalidate(chatRoomJoinedProvider);
if (context.mounted) {
context.pop();
}
}
});
},
),
],
);
}
}
@@ -576,11 +608,10 @@ class ChatMemberListNotifier extends AsyncNotifier<List<SnChatMember>>
);
totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final members =
response.data
.map((e) => SnChatMember.fromJson(e))
.cast<SnChatMember>()
.toList();
final members = response.data
.map((e) => SnChatMember.fromJson(e))
.cast<SnChatMember>()
.toList();
return members;
}