Compare commits

...

24 Commits

Author SHA1 Message Date
c7f417234e Show awarded score 2025-09-08 23:55:50 +08:00
6c847ee1e1 👽 Remove order handle in stellar program purchase 2025-09-08 22:50:32 +08:00
18ad4d376e 💄 The payment now no longer auto procced 2025-09-08 22:48:46 +08:00
c4d5ba5c9d 🐛 Fix inline attachment didn't render properly 2025-09-08 22:43:41 +08:00
1069669049 🐛 Post visibility not readable 2025-09-08 22:26:44 +08:00
aa648fec62 Reworked post draft 2025-09-08 22:25:54 +08:00
541900673a 🐛 Disable unix socks completely on macOS 2025-09-08 21:49:23 +08:00
265502ffd0 🐛 Disable ipc rpc server on macos 2025-09-08 21:34:03 +08:00
3bd79350d1 🧱 Activity RPC server 2025-09-08 20:57:27 +08:00
5294d1fb23 💄 Make sure cloud file background fill entire space 2025-09-08 19:29:01 +08:00
ec1269dcf1 💄 Optimize check in widget and add today's countdown 2025-09-08 19:27:30 +08:00
edb0a25f34 💄 Optimize embed view renderer loading logic 2025-09-08 15:48:10 +08:00
7cd10118cc Post award 2025-09-08 15:47:57 +08:00
fcddc8f345 💄 Optimize embed webview 2025-09-08 02:45:20 +08:00
1cc34240da Post embed view rendering 2025-09-08 02:42:49 +08:00
013f7f02bc Post embed view 2025-09-08 02:15:22 +08:00
4e79e4100f Public contact method 2025-09-08 00:40:32 +08:00
feda1f067f 🐛 Post detail initial expand poll 2025-09-07 23:57:14 +08:00
fe0e192a43 💄 Optimize error alert 2025-09-07 23:56:58 +08:00
93df294142 Poll collapse 2025-09-07 23:00:29 +08:00
78d65c39f3 🐛 Fix poll yes or no stats 2025-09-07 22:48:22 +08:00
18b0dbd797 🐛 Fix post submit 2025-09-07 22:20:49 +08:00
80cc8cbb40 🐛 Fix android build 2025-09-07 18:30:35 +08:00
646e95a9fc 💄 Optimize check in 2025-09-07 18:30:28 +08:00
40 changed files with 3187 additions and 483 deletions

View File

@@ -24,6 +24,8 @@ android {
ndkVersion = "29.0.13113456"
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
@@ -63,6 +65,8 @@ android {
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
implementation("com.google.android.material:material:1.12.0")
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("com.squareup.okhttp3:okhttp:5.1.0")

View File

@@ -133,6 +133,25 @@
"other": "{} replies"
},
"forward": "Forward",
"award": "Award",
"awardPost": "Award Post",
"awardMessage": "Message",
"awardMessageHint": "Enter your award message...",
"awardAttitude": "Attitude",
"awardAttitudePositive": "Positive",
"awardAttitudeNegative": "Negative",
"awardAmount": "Amount",
"awardAmountHint": "Enter amount...",
"awardAmountRequired": "Amount is required",
"awardAmountInvalid": "Please enter a valid amount",
"awardMessageTooLong": "Message is too long (max 4096 characters)",
"awardSuccess": "Award sent successfully!",
"awardSubmit": "Award",
"awardPostPreview": "Post Preview",
"awardNoContent": "No content available",
"awardByPublisher": "By {}",
"awardBenefits": "Award Benefits",
"awardBenefitsDescription": "Awarding this post increases its value and visibility. Higher valued posts have a better chance of being featured and highlighted in the community.",
"repliedTo": "Replied to",
"forwarded": "Forwarded",
"hasAttachments": {
@@ -452,6 +471,8 @@
"close": "Close",
"drafts": "Drafts",
"noDrafts": "No drafts yet",
"searchDrafts": "Search drafts...",
"noSearchResults": "No search results",
"articleDrafts": "Article drafts",
"postDrafts": "Post drafts",
"saveDraft": "Save draft",
@@ -498,6 +519,10 @@
"contactMethodSetPrimary": "Set as Primary",
"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.",
"contactMethodMakePublic": "Make Public",
"contactMethodMakePrivate": "Make Private",
"contactMethodPublic": "Public",
"contactMethodPrivate": "Private",
"chatNotifyLevel": "Notify Level",
"chatNotifyLevelDescription": "Decide how many notifications you will receive.",
"chatNotifyLevelAll": "All",
@@ -977,5 +1002,19 @@
"pinned": "Pinned",
"noResultsFound": "No results found",
"toggleFilters": "Toggle filters",
"notableDayNext": "{} is in"
"notableDayNext": "{} is in",
"expandPoll": "Expand Poll",
"collapsePoll": "Collapse Poll",
"embedView": "Embed View",
"embedUri": "Embed URI",
"aspectRatio": "Aspect Ratio",
"renderer": "Renderer",
"addEmbed": "Add Embed",
"editEmbed": "Edit Embed",
"deleteEmbed": "Delete Embed",
"deleteEmbedConfirm": "Are you sure you want to delete this embed?",
"currentEmbed": "Current Embed",
"noEmbed": "No embed yet",
"save": "Save",
"webView": "Web View"
}

View File

@@ -2,8 +2,15 @@ import 'package:drift/drift.dart';
class PostDrafts extends Table {
TextColumn get id => text()();
TextColumn get post => text()(); // Store SnPost model as JSON string
// Searchable fields stored separately for performance
TextColumn get title => text().nullable()();
TextColumn get description => text().nullable()();
TextColumn get content => text().nullable()();
IntColumn get visibility => integer().withDefault(const Constant(0))();
IntColumn get type => integer().withDefault(const Constant(0))();
DateTimeColumn get lastModified => dateTime()();
// Full post data stored as JSON for complete restoration
TextColumn get postData => text()();
@override
Set<Column> get primaryKey => {id};

View File

@@ -12,7 +12,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase(super.e);
@override
int get schemaVersion => 4;
int get schemaVersion => 6;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -28,9 +28,67 @@ class AppDatabase extends _$AppDatabase {
// Drop old draft tables if they exist
await m.createTable(postDrafts);
}
if (from < 6) {
// Migrate from old schema to new schema with separate searchable fields
await _migrateToVersion6(m);
}
},
);
Future<void> _migrateToVersion6(Migrator m) async {
// Rename existing table to old if it exists
try {
await customStatement(
'ALTER TABLE post_drafts RENAME TO post_drafts_old',
);
} catch (e) {
// Table might not exist
}
// Drop the table
await customStatement('DROP TABLE IF EXISTS post_drafts');
// Create new table
await m.createTable(postDrafts);
// Migrate existing data if any
try {
final oldDrafts =
await customSelect(
'SELECT id, post, lastModified FROM post_drafts_old',
readsFrom: {postDrafts},
).get();
for (final row in oldDrafts) {
final postJson = row.read<String>('post');
final id = row.read<String>('id');
final lastModified = row.read<DateTime>('lastModified');
if (postJson.isNotEmpty) {
final post = SnPost.fromJson(jsonDecode(postJson));
await into(postDrafts).insert(
PostDraftsCompanion(
id: Value(id),
title: Value(post.title),
description: Value(post.description),
content: Value(post.content),
visibility: Value(post.visibility),
type: Value(post.type),
lastModified: Value(lastModified),
postData: Value(postJson),
),
);
}
}
// Drop old table
await customStatement('DROP TABLE IF EXISTS post_drafts_old');
} catch (e) {
// If migration fails, just recreate the table
await m.createTable(postDrafts);
}
}
// Methods for chat messages
Future<List<ChatMessage>> getMessagesForRoom(
String roomId, {
@@ -69,7 +127,9 @@ class AppDatabase extends _$AppDatabase {
}
Future<int> getTotalMessagesForRoom(String roomId) {
return (select(chatMessages)..where((m) => m.roomId.equals(roomId))).get().then((list) => list.length);
return (select(
chatMessages,
)..where((m) => m.roomId.equals(roomId))).get().then((list) => list.length);
}
Future<List<LocalChatMessage>> searchMessages(
@@ -85,10 +145,6 @@ class AppDatabase extends _$AppDatabase {
..where((m) => m.content.like('%${query.toLowerCase()}%'));
}
final messages =
await (selectStatement
..orderBy([(m) => OrderingTerm.desc(m.createdAt)]))
@@ -129,10 +185,31 @@ class AppDatabase extends _$AppDatabase {
Future<List<SnPost>> getAllPostDrafts() async {
final drafts = await select(postDrafts).get();
return drafts
.map((draft) => SnPost.fromJson(jsonDecode(draft.post)))
.map((draft) => SnPost.fromJson(jsonDecode(draft.postData)))
.toList();
}
Future<List<PostDraft>> getAllPostDraftRecords() async {
return await select(postDrafts).get();
}
Future<List<PostDraft>> searchPostDrafts(String query) async {
if (query.isEmpty) {
return await select(postDrafts).get();
}
final searchTerm = '%${query.toLowerCase()}%';
return await (select(postDrafts)
..where(
(draft) =>
draft.title.like(searchTerm) |
draft.description.like(searchTerm) |
draft.content.like(searchTerm),
)
..orderBy([(draft) => OrderingTerm.desc(draft.lastModified)]))
.get();
}
Future<void> addPostDraft(PostDraftsCompanion entry) async {
await into(postDrafts).insert(entry, mode: InsertMode.replace);
}
@@ -144,4 +221,9 @@ class AppDatabase extends _$AppDatabase {
Future<void> clearAllPostDrafts() async {
await delete(postDrafts).go();
}
Future<PostDraft?> getPostDraftById(String id) async {
return await (select(postDrafts)
..where((tbl) => tbl.id.equals(id))).getSingleOrNull();
}
}

View File

@@ -584,14 +584,58 @@ class $PostDraftsTable extends PostDrafts
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _postMeta = const VerificationMeta('post');
static const VerificationMeta _titleMeta = const VerificationMeta('title');
@override
late final GeneratedColumn<String> post = GeneratedColumn<String>(
'post',
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _descriptionMeta = const VerificationMeta(
'description',
);
@override
late final GeneratedColumn<String> description = GeneratedColumn<String>(
'description',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _contentMeta = const VerificationMeta(
'content',
);
@override
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'content',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _visibilityMeta = const VerificationMeta(
'visibility',
);
@override
late final GeneratedColumn<int> visibility = GeneratedColumn<int>(
'visibility',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0),
);
static const VerificationMeta _typeMeta = const VerificationMeta('type');
@override
late final GeneratedColumn<int> type = GeneratedColumn<int>(
'type',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0),
);
static const VerificationMeta _lastModifiedMeta = const VerificationMeta(
'lastModified',
@@ -604,8 +648,28 @@ class $PostDraftsTable extends PostDrafts
type: DriftSqlType.dateTime,
requiredDuringInsert: true,
);
static const VerificationMeta _postDataMeta = const VerificationMeta(
'postData',
);
@override
List<GeneratedColumn> get $columns => [id, post, lastModified];
late final GeneratedColumn<String> postData = GeneratedColumn<String>(
'post_data',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
@override
List<GeneratedColumn> get $columns => [
id,
title,
description,
content,
visibility,
type,
lastModified,
postData,
];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -623,13 +687,38 @@ class $PostDraftsTable extends PostDrafts
} else if (isInserting) {
context.missing(_idMeta);
}
if (data.containsKey('post')) {
if (data.containsKey('title')) {
context.handle(
_postMeta,
post.isAcceptableOrUnknown(data['post']!, _postMeta),
_titleMeta,
title.isAcceptableOrUnknown(data['title']!, _titleMeta),
);
}
if (data.containsKey('description')) {
context.handle(
_descriptionMeta,
description.isAcceptableOrUnknown(
data['description']!,
_descriptionMeta,
),
);
}
if (data.containsKey('content')) {
context.handle(
_contentMeta,
content.isAcceptableOrUnknown(data['content']!, _contentMeta),
);
}
if (data.containsKey('visibility')) {
context.handle(
_visibilityMeta,
visibility.isAcceptableOrUnknown(data['visibility']!, _visibilityMeta),
);
}
if (data.containsKey('type')) {
context.handle(
_typeMeta,
type.isAcceptableOrUnknown(data['type']!, _typeMeta),
);
} else if (isInserting) {
context.missing(_postMeta);
}
if (data.containsKey('last_modified')) {
context.handle(
@@ -642,6 +731,14 @@ class $PostDraftsTable extends PostDrafts
} else if (isInserting) {
context.missing(_lastModifiedMeta);
}
if (data.containsKey('post_data')) {
context.handle(
_postDataMeta,
postData.isAcceptableOrUnknown(data['post_data']!, _postDataMeta),
);
} else if (isInserting) {
context.missing(_postDataMeta);
}
return context;
}
@@ -656,16 +753,38 @@ class $PostDraftsTable extends PostDrafts
DriftSqlType.string,
data['${effectivePrefix}id'],
)!,
post:
title: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}title'],
),
description: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}description'],
),
content: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}content'],
),
visibility:
attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}post'],
DriftSqlType.int,
data['${effectivePrefix}visibility'],
)!,
type:
attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}type'],
)!,
lastModified:
attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}last_modified'],
)!,
postData:
attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}post_data'],
)!,
);
}
@@ -677,27 +796,60 @@ class $PostDraftsTable extends PostDrafts
class PostDraft extends DataClass implements Insertable<PostDraft> {
final String id;
final String post;
final String? title;
final String? description;
final String? content;
final int visibility;
final int type;
final DateTime lastModified;
final String postData;
const PostDraft({
required this.id,
required this.post,
this.title,
this.description,
this.content,
required this.visibility,
required this.type,
required this.lastModified,
required this.postData,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<String>(id);
map['post'] = Variable<String>(post);
if (!nullToAbsent || title != null) {
map['title'] = Variable<String>(title);
}
if (!nullToAbsent || description != null) {
map['description'] = Variable<String>(description);
}
if (!nullToAbsent || content != null) {
map['content'] = Variable<String>(content);
}
map['visibility'] = Variable<int>(visibility);
map['type'] = Variable<int>(type);
map['last_modified'] = Variable<DateTime>(lastModified);
map['post_data'] = Variable<String>(postData);
return map;
}
PostDraftsCompanion toCompanion(bool nullToAbsent) {
return PostDraftsCompanion(
id: Value(id),
post: Value(post),
title:
title == null && nullToAbsent ? const Value.absent() : Value(title),
description:
description == null && nullToAbsent
? const Value.absent()
: Value(description),
content:
content == null && nullToAbsent
? const Value.absent()
: Value(content),
visibility: Value(visibility),
type: Value(type),
lastModified: Value(lastModified),
postData: Value(postData),
);
}
@@ -708,8 +860,13 @@ class PostDraft extends DataClass implements Insertable<PostDraft> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return PostDraft(
id: serializer.fromJson<String>(json['id']),
post: serializer.fromJson<String>(json['post']),
title: serializer.fromJson<String?>(json['title']),
description: serializer.fromJson<String?>(json['description']),
content: serializer.fromJson<String?>(json['content']),
visibility: serializer.fromJson<int>(json['visibility']),
type: serializer.fromJson<int>(json['type']),
lastModified: serializer.fromJson<DateTime>(json['lastModified']),
postData: serializer.fromJson<String>(json['postData']),
);
}
@override
@@ -717,25 +874,50 @@ class PostDraft extends DataClass implements Insertable<PostDraft> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<String>(id),
'post': serializer.toJson<String>(post),
'title': serializer.toJson<String?>(title),
'description': serializer.toJson<String?>(description),
'content': serializer.toJson<String?>(content),
'visibility': serializer.toJson<int>(visibility),
'type': serializer.toJson<int>(type),
'lastModified': serializer.toJson<DateTime>(lastModified),
'postData': serializer.toJson<String>(postData),
};
}
PostDraft copyWith({String? id, String? post, DateTime? lastModified}) =>
PostDraft(
id: id ?? this.id,
post: post ?? this.post,
lastModified: lastModified ?? this.lastModified,
);
PostDraft copyWith({
String? id,
Value<String?> title = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<String?> content = const Value.absent(),
int? visibility,
int? type,
DateTime? lastModified,
String? postData,
}) => PostDraft(
id: id ?? this.id,
title: title.present ? title.value : this.title,
description: description.present ? description.value : this.description,
content: content.present ? content.value : this.content,
visibility: visibility ?? this.visibility,
type: type ?? this.type,
lastModified: lastModified ?? this.lastModified,
postData: postData ?? this.postData,
);
PostDraft copyWithCompanion(PostDraftsCompanion data) {
return PostDraft(
id: data.id.present ? data.id.value : this.id,
post: data.post.present ? data.post.value : this.post,
title: data.title.present ? data.title.value : this.title,
description:
data.description.present ? data.description.value : this.description,
content: data.content.present ? data.content.value : this.content,
visibility:
data.visibility.present ? data.visibility.value : this.visibility,
type: data.type.present ? data.type.value : this.type,
lastModified:
data.lastModified.present
? data.lastModified.value
: this.lastModified,
postData: data.postData.present ? data.postData.value : this.postData,
);
}
@@ -743,66 +925,120 @@ class PostDraft extends DataClass implements Insertable<PostDraft> {
String toString() {
return (StringBuffer('PostDraft(')
..write('id: $id, ')
..write('post: $post, ')
..write('lastModified: $lastModified')
..write('title: $title, ')
..write('description: $description, ')
..write('content: $content, ')
..write('visibility: $visibility, ')
..write('type: $type, ')
..write('lastModified: $lastModified, ')
..write('postData: $postData')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, post, lastModified);
int get hashCode => Object.hash(
id,
title,
description,
content,
visibility,
type,
lastModified,
postData,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is PostDraft &&
other.id == this.id &&
other.post == this.post &&
other.lastModified == this.lastModified);
other.title == this.title &&
other.description == this.description &&
other.content == this.content &&
other.visibility == this.visibility &&
other.type == this.type &&
other.lastModified == this.lastModified &&
other.postData == this.postData);
}
class PostDraftsCompanion extends UpdateCompanion<PostDraft> {
final Value<String> id;
final Value<String> post;
final Value<String?> title;
final Value<String?> description;
final Value<String?> content;
final Value<int> visibility;
final Value<int> type;
final Value<DateTime> lastModified;
final Value<String> postData;
final Value<int> rowid;
const PostDraftsCompanion({
this.id = const Value.absent(),
this.post = const Value.absent(),
this.title = const Value.absent(),
this.description = const Value.absent(),
this.content = const Value.absent(),
this.visibility = const Value.absent(),
this.type = const Value.absent(),
this.lastModified = const Value.absent(),
this.postData = const Value.absent(),
this.rowid = const Value.absent(),
});
PostDraftsCompanion.insert({
required String id,
required String post,
this.title = const Value.absent(),
this.description = const Value.absent(),
this.content = const Value.absent(),
this.visibility = const Value.absent(),
this.type = const Value.absent(),
required DateTime lastModified,
required String postData,
this.rowid = const Value.absent(),
}) : id = Value(id),
post = Value(post),
lastModified = Value(lastModified);
lastModified = Value(lastModified),
postData = Value(postData);
static Insertable<PostDraft> custom({
Expression<String>? id,
Expression<String>? post,
Expression<String>? title,
Expression<String>? description,
Expression<String>? content,
Expression<int>? visibility,
Expression<int>? type,
Expression<DateTime>? lastModified,
Expression<String>? postData,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (post != null) 'post': post,
if (title != null) 'title': title,
if (description != null) 'description': description,
if (content != null) 'content': content,
if (visibility != null) 'visibility': visibility,
if (type != null) 'type': type,
if (lastModified != null) 'last_modified': lastModified,
if (postData != null) 'post_data': postData,
if (rowid != null) 'rowid': rowid,
});
}
PostDraftsCompanion copyWith({
Value<String>? id,
Value<String>? post,
Value<String?>? title,
Value<String?>? description,
Value<String?>? content,
Value<int>? visibility,
Value<int>? type,
Value<DateTime>? lastModified,
Value<String>? postData,
Value<int>? rowid,
}) {
return PostDraftsCompanion(
id: id ?? this.id,
post: post ?? this.post,
title: title ?? this.title,
description: description ?? this.description,
content: content ?? this.content,
visibility: visibility ?? this.visibility,
type: type ?? this.type,
lastModified: lastModified ?? this.lastModified,
postData: postData ?? this.postData,
rowid: rowid ?? this.rowid,
);
}
@@ -813,12 +1049,27 @@ class PostDraftsCompanion extends UpdateCompanion<PostDraft> {
if (id.present) {
map['id'] = Variable<String>(id.value);
}
if (post.present) {
map['post'] = Variable<String>(post.value);
if (title.present) {
map['title'] = Variable<String>(title.value);
}
if (description.present) {
map['description'] = Variable<String>(description.value);
}
if (content.present) {
map['content'] = Variable<String>(content.value);
}
if (visibility.present) {
map['visibility'] = Variable<int>(visibility.value);
}
if (type.present) {
map['type'] = Variable<int>(type.value);
}
if (lastModified.present) {
map['last_modified'] = Variable<DateTime>(lastModified.value);
}
if (postData.present) {
map['post_data'] = Variable<String>(postData.value);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
@@ -829,8 +1080,13 @@ class PostDraftsCompanion extends UpdateCompanion<PostDraft> {
String toString() {
return (StringBuffer('PostDraftsCompanion(')
..write('id: $id, ')
..write('post: $post, ')
..write('title: $title, ')
..write('description: $description, ')
..write('content: $content, ')
..write('visibility: $visibility, ')
..write('type: $type, ')
..write('lastModified: $lastModified, ')
..write('postData: $postData, ')
..write('rowid: $rowid')
..write(')'))
.toString();
@@ -1140,15 +1396,25 @@ typedef $$ChatMessagesTableProcessedTableManager =
typedef $$PostDraftsTableCreateCompanionBuilder =
PostDraftsCompanion Function({
required String id,
required String post,
Value<String?> title,
Value<String?> description,
Value<String?> content,
Value<int> visibility,
Value<int> type,
required DateTime lastModified,
required String postData,
Value<int> rowid,
});
typedef $$PostDraftsTableUpdateCompanionBuilder =
PostDraftsCompanion Function({
Value<String> id,
Value<String> post,
Value<String?> title,
Value<String?> description,
Value<String?> content,
Value<int> visibility,
Value<int> type,
Value<DateTime> lastModified,
Value<String> postData,
Value<int> rowid,
});
@@ -1166,8 +1432,28 @@ class $$PostDraftsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get post => $composableBuilder(
column: $table.post,
ColumnFilters<String> get title => $composableBuilder(
column: $table.title,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get description => $composableBuilder(
column: $table.description,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get content => $composableBuilder(
column: $table.content,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get visibility => $composableBuilder(
column: $table.visibility,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get type => $composableBuilder(
column: $table.type,
builder: (column) => ColumnFilters(column),
);
@@ -1175,6 +1461,11 @@ class $$PostDraftsTableFilterComposer
column: $table.lastModified,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get postData => $composableBuilder(
column: $table.postData,
builder: (column) => ColumnFilters(column),
);
}
class $$PostDraftsTableOrderingComposer
@@ -1191,8 +1482,28 @@ class $$PostDraftsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get post => $composableBuilder(
column: $table.post,
ColumnOrderings<String> get title => $composableBuilder(
column: $table.title,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get description => $composableBuilder(
column: $table.description,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get content => $composableBuilder(
column: $table.content,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get visibility => $composableBuilder(
column: $table.visibility,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get type => $composableBuilder(
column: $table.type,
builder: (column) => ColumnOrderings(column),
);
@@ -1200,6 +1511,11 @@ class $$PostDraftsTableOrderingComposer
column: $table.lastModified,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get postData => $composableBuilder(
column: $table.postData,
builder: (column) => ColumnOrderings(column),
);
}
class $$PostDraftsTableAnnotationComposer
@@ -1214,13 +1530,32 @@ class $$PostDraftsTableAnnotationComposer
GeneratedColumn<String> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get post =>
$composableBuilder(column: $table.post, builder: (column) => column);
GeneratedColumn<String> get title =>
$composableBuilder(column: $table.title, builder: (column) => column);
GeneratedColumn<String> get description => $composableBuilder(
column: $table.description,
builder: (column) => column,
);
GeneratedColumn<String> get content =>
$composableBuilder(column: $table.content, builder: (column) => column);
GeneratedColumn<int> get visibility => $composableBuilder(
column: $table.visibility,
builder: (column) => column,
);
GeneratedColumn<int> get type =>
$composableBuilder(column: $table.type, builder: (column) => column);
GeneratedColumn<DateTime> get lastModified => $composableBuilder(
column: $table.lastModified,
builder: (column) => column,
);
GeneratedColumn<String> get postData =>
$composableBuilder(column: $table.postData, builder: (column) => column);
}
class $$PostDraftsTableTableManager
@@ -1255,25 +1590,45 @@ class $$PostDraftsTableTableManager
updateCompanionCallback:
({
Value<String> id = const Value.absent(),
Value<String> post = const Value.absent(),
Value<String?> title = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<String?> content = const Value.absent(),
Value<int> visibility = const Value.absent(),
Value<int> type = const Value.absent(),
Value<DateTime> lastModified = const Value.absent(),
Value<String> postData = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => PostDraftsCompanion(
id: id,
post: post,
title: title,
description: description,
content: content,
visibility: visibility,
type: type,
lastModified: lastModified,
postData: postData,
rowid: rowid,
),
createCompanionCallback:
({
required String id,
required String post,
Value<String?> title = const Value.absent(),
Value<String?> description = const Value.absent(),
Value<String?> content = const Value.absent(),
Value<int> visibility = const Value.absent(),
Value<int> type = const Value.absent(),
required DateTime lastModified,
required String postData,
Value<int> rowid = const Value.absent(),
}) => PostDraftsCompanion.insert(
id: id,
post: post,
title: title,
description: description,
content: content,
visibility: visibility,
type: type,
lastModified: lastModified,
postData: postData,
rowid: rowid,
),
withReferenceMapper:

View File

@@ -19,6 +19,7 @@ sealed class SnAccount with _$SnAccount {
required SnAccountProfile profile,
required SnWalletSubscriptionRef? perkSubscription,
@Default([]) List<SnAccountBadge> badges,
@Default([]) List<SnContactMethod> contacts,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
@@ -135,6 +136,7 @@ sealed class SnContactMethod with _$SnContactMethod {
required int type,
required DateTime? verifiedAt,
required bool isPrimary,
required bool isPublic,
required String content,
required String accountId,
required DateTime createdAt,

View File

@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnAccount {
String get id; String get name; String get nick; String get language; String get region; bool get isSuperuser; String? get automatedId; SnAccountProfile get profile; SnWalletSubscriptionRef? get perkSubscription; List<SnAccountBadge> get badges; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; String get name; String get nick; String get language; String get region; bool get isSuperuser; String? get automatedId; SnAccountProfile get profile; SnWalletSubscriptionRef? get perkSubscription; List<SnAccountBadge> get badges; List<SnContactMethod> get contacts; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnAccount
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $SnAccountCopyWith<SnAccount> get copyWith => _$SnAccountCopyWithImpl<SnAccount>
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.region, region) || other.region == region)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other.badges, badges)&&(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 SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.region, region) || other.region == region)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other.badges, badges)&&const DeepCollectionEquality().equals(other.contacts, contacts)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,nick,language,region,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(badges),createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,name,nick,language,region,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(badges),const DeepCollectionEquality().hash(contacts),createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, region: $region, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, region: $region, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, contacts: $contacts, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -48,7 +48,7 @@ abstract mixin class $SnAccountCopyWith<$Res> {
factory $SnAccountCopyWith(SnAccount value, $Res Function(SnAccount) _then) = _$SnAccountCopyWithImpl;
@useResult
$Res call({
String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, List<SnContactMethod> contacts, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -65,7 +65,7 @@ class _$SnAccountCopyWithImpl<$Res>
/// Create a copy of SnAccount
/// 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? nick = null,Object? language = null,Object? region = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? region = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? contacts = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
@@ -77,7 +77,8 @@ as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId //
as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable
as SnWalletSubscriptionRef?,badges: null == badges ? _self.badges : badges // ignore: cast_nullable_to_non_nullable
as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as List<SnAccountBadge>,contacts: null == contacts ? _self.contacts : contacts // ignore: cast_nullable_to_non_nullable
as List<SnContactMethod>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
@@ -183,10 +184,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, 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 nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, List<SnContactMethod> contacts, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnAccount() when $default != null:
return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.contacts,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
}
@@ -204,10 +205,10 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, List<SnContactMethod> contacts, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnAccount():
return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);}
return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.contacts,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -221,10 +222,10 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, List<SnContactMethod> contacts, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnAccount() when $default != null:
return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.contacts,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
}
@@ -236,7 +237,7 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.region,_that
@JsonSerializable()
class _SnAccount implements SnAccount {
const _SnAccount({required this.id, required this.name, required this.nick, required this.language, this.region = "", required this.isSuperuser, required this.automatedId, required this.profile, required this.perkSubscription, final List<SnAccountBadge> badges = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges;
const _SnAccount({required this.id, required this.name, required this.nick, required this.language, this.region = "", required this.isSuperuser, required this.automatedId, required this.profile, required this.perkSubscription, final List<SnAccountBadge> badges = const [], final List<SnContactMethod> contacts = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges,_contacts = contacts;
factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json);
@override final String id;
@@ -255,6 +256,13 @@ class _SnAccount implements SnAccount {
return EqualUnmodifiableListView(_badges);
}
final List<SnContactMethod> _contacts;
@override@JsonKey() List<SnContactMethod> get contacts {
if (_contacts is EqualUnmodifiableListView) return _contacts;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_contacts);
}
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
@@ -272,16 +280,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.region, region) || other.region == region)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other._badges, _badges)&&(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 _SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.region, region) || other.region == region)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other._badges, _badges)&&const DeepCollectionEquality().equals(other._contacts, _contacts)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,name,nick,language,region,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(_badges),createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,name,nick,language,region,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(_badges),const DeepCollectionEquality().hash(_contacts),createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, region: $region, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, region: $region, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, contacts: $contacts, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -292,7 +300,7 @@ abstract mixin class _$SnAccountCopyWith<$Res> implements $SnAccountCopyWith<$Re
factory _$SnAccountCopyWith(_SnAccount value, $Res Function(_SnAccount) _then) = __$SnAccountCopyWithImpl;
@override @useResult
$Res call({
String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, String name, String nick, String language, String region, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, List<SnContactMethod> contacts, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -309,7 +317,7 @@ class __$SnAccountCopyWithImpl<$Res>
/// Create a copy of SnAccount
/// 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? nick = null,Object? language = null,Object? region = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? region = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? contacts = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAccount(
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
@@ -321,7 +329,8 @@ as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId //
as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable
as SnWalletSubscriptionRef?,badges: null == badges ? _self._badges : badges // ignore: cast_nullable_to_non_nullable
as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as List<SnAccountBadge>,contacts: null == contacts ? _self._contacts : contacts // ignore: cast_nullable_to_non_nullable
as List<SnContactMethod>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
@@ -1627,7 +1636,7 @@ as DateTime?,
/// @nodoc
mixin _$SnContactMethod {
String get id; int get type; DateTime? get verifiedAt; bool get isPrimary; String get content; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; int get type; DateTime? get verifiedAt; bool get isPrimary; bool get isPublic; String get content; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnContactMethod
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -1640,16 +1649,16 @@ $SnContactMethodCopyWith<SnContactMethod> get copyWith => _$SnContactMethodCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,isPublic,content,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, isPublic: $isPublic, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -1660,7 +1669,7 @@ abstract mixin class $SnContactMethodCopyWith<$Res> {
factory $SnContactMethodCopyWith(SnContactMethod value, $Res Function(SnContactMethod) _then) = _$SnContactMethodCopyWithImpl;
@useResult
$Res call({
String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, int type, DateTime? verifiedAt, bool isPrimary, bool isPublic, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -1677,12 +1686,13 @@ class _$SnContactMethodCopyWithImpl<$Res>
/// Create a copy of SnContactMethod
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? isPublic = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable
as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
@@ -1770,10 +1780,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, int type, DateTime? verifiedAt, bool isPrimary, bool isPublic, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnContactMethod() when $default != null:
return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.isPublic,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
}
@@ -1791,10 +1801,10 @@ return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.conte
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, int type, DateTime? verifiedAt, bool isPrimary, bool isPublic, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnContactMethod():
return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.isPublic,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -1808,10 +1818,10 @@ return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.conte
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, int type, DateTime? verifiedAt, bool isPrimary, bool isPublic, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnContactMethod() when $default != null:
return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.isPublic,_that.content,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
}
@@ -1823,13 +1833,14 @@ return $default(_that.id,_that.type,_that.verifiedAt,_that.isPrimary,_that.conte
@JsonSerializable()
class _SnContactMethod implements SnContactMethod {
const _SnContactMethod({required this.id, required this.type, required this.verifiedAt, required this.isPrimary, required this.content, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
const _SnContactMethod({required this.id, required this.type, required this.verifiedAt, required this.isPrimary, required this.isPublic, required this.content, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnContactMethod.fromJson(Map<String, dynamic> json) => _$SnContactMethodFromJson(json);
@override final String id;
@override final int type;
@override final DateTime? verifiedAt;
@override final bool isPrimary;
@override final bool isPublic;
@override final String content;
@override final String accountId;
@override final DateTime createdAt;
@@ -1849,16 +1860,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnContactMethod&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.verifiedAt, verifiedAt) || other.verifiedAt == verifiedAt)&&(identical(other.isPrimary, isPrimary) || other.isPrimary == isPrimary)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.content, content) || other.content == content)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,content,accountId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,type,verifiedAt,isPrimary,isPublic,content,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnContactMethod(id: $id, type: $type, verifiedAt: $verifiedAt, isPrimary: $isPrimary, isPublic: $isPublic, content: $content, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -1869,7 +1880,7 @@ abstract mixin class _$SnContactMethodCopyWith<$Res> implements $SnContactMethod
factory _$SnContactMethodCopyWith(_SnContactMethod value, $Res Function(_SnContactMethod) _then) = __$SnContactMethodCopyWithImpl;
@override @useResult
$Res call({
String id, int type, DateTime? verifiedAt, bool isPrimary, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, int type, DateTime? verifiedAt, bool isPrimary, bool isPublic, String content, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -1886,12 +1897,13 @@ class __$SnContactMethodCopyWithImpl<$Res>
/// Create a copy of SnContactMethod
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? verifiedAt = freezed,Object? isPrimary = null,Object? isPublic = null,Object? content = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnContactMethod(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,verifiedAt: freezed == verifiedAt ? _self.verifiedAt : verifiedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,isPrimary: null == isPrimary ? _self.isPrimary : isPrimary // ignore: cast_nullable_to_non_nullable
as bool,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
as bool,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable

View File

@@ -26,6 +26,11 @@ _SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount(
?.map((e) => SnAccountBadge.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
contacts:
(json['contacts'] as List<dynamic>?)
?.map((e) => SnContactMethod.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
@@ -46,6 +51,7 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) =>
'profile': instance.profile.toJson(),
'perk_subscription': instance.perkSubscription?.toJson(),
'badges': instance.badges.map((e) => e.toJson()).toList(),
'contacts': instance.contacts.map((e) => e.toJson()).toList(),
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
@@ -229,6 +235,7 @@ _SnContactMethod _$SnContactMethodFromJson(Map<String, dynamic> json) =>
? null
: DateTime.parse(json['verified_at'] as String),
isPrimary: json['is_primary'] as bool,
isPublic: json['is_public'] as bool,
content: json['content'] as String,
accountId: json['account_id'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
@@ -245,6 +252,7 @@ Map<String, dynamic> _$SnContactMethodToJson(_SnContactMethod instance) =>
'type': instance.type,
'verified_at': instance.verifiedAt?.toIso8601String(),
'is_primary': instance.isPrimary,
'is_public': instance.isPublic,
'content': instance.content,
'account_id': instance.accountId,
'created_at': instance.createdAt.toIso8601String(),

View File

@@ -22,11 +22,13 @@ sealed class SnPost with _$SnPost {
String? slug,
@Default(0) int type,
Map<String, dynamic>? meta,
SnPostEmbedView? embedView,
@Default(0) int viewsUnique,
@Default(0) int viewsTotal,
@Default(0) int upvotes,
@Default(0) int downvotes,
@Default(0) int repliesCount,
@Default(0) int awardedScore,
int? pinMode,
String? threadedPostId,
SnPost? threadedPost,
@@ -105,3 +107,20 @@ const Map<String, ReactInfo> kReactionTemplates = {
'pray': ReactInfo(icon: '🙏', attitude: 0),
'heart': ReactInfo(icon: '❤️', attitude: 0),
};
enum PostEmbedViewRenderer {
@JsonValue(0)
webView,
}
@freezed
sealed class SnPostEmbedView with _$SnPostEmbedView {
const factory SnPostEmbedView({
required String uri,
double? aspectRatio,
@Default(PostEmbedViewRenderer.webView) PostEmbedViewRenderer renderer,
}) = _SnPostEmbedView;
factory SnPostEmbedView.fromJson(Map<String, dynamic> json) =>
_$SnPostEmbedViewFromJson(json);
}

View File

@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnPost {
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; String? get slug; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; int? get pinMode; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; String? get realmId; SnRealm? get realm; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; String? get slug; int get type; Map<String, dynamic>? get meta; SnPostEmbedView? get embedView; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; int get awardedScore; int? get pinMode; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; String? get realmId; SnRealm? get realm; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<SnPostTag> get tags; List<SnPostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.embedView, embedView) || other.embedView == embedView)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.awardedScore, awardedScore) || other.awardedScore == awardedScore)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(meta),embedView,viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,awardedScore,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
@override
String toString() {
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, embedView: $embedView, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, awardedScore: $awardedScore, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
}
@@ -48,11 +48,11 @@ abstract mixin class $SnPostCopyWith<$Res> {
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
@useResult
$Res call({
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, SnPostEmbedView? embedView, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int awardedScore, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
});
$SnPostCopyWith<$Res>? get threadedPost;$SnPostCopyWith<$Res>? get repliedPost;$SnPostCopyWith<$Res>? get forwardedPost;$SnRealmCopyWith<$Res>? get realm;$SnPublisherCopyWith<$Res> get publisher;
$SnPostEmbedViewCopyWith<$Res>? get embedView;$SnPostCopyWith<$Res>? get threadedPost;$SnPostCopyWith<$Res>? get repliedPost;$SnPostCopyWith<$Res>? get forwardedPost;$SnRealmCopyWith<$Res>? get realm;$SnPublisherCopyWith<$Res> get publisher;
}
/// @nodoc
@@ -65,7 +65,7 @@ class _$SnPostCopyWithImpl<$Res>
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? embedView = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? awardedScore = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@@ -78,11 +78,13 @@ as int,content: freezed == content ? _self.content : content // ignore: cast_nul
as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,embedView: freezed == embedView ? _self.embedView : embedView // ignore: cast_nullable_to_non_nullable
as SnPostEmbedView?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable
as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable
as int,awardedScore: null == awardedScore ? _self.awardedScore : awardedScore // ignore: cast_nullable_to_non_nullable
as int,pinMode: freezed == pinMode ? _self.pinMode : pinMode // ignore: cast_nullable_to_non_nullable
as int?,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable
as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable
@@ -111,6 +113,18 @@ as bool,
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPostEmbedViewCopyWith<$Res>? get embedView {
if (_self.embedView == null) {
return null;
}
return $SnPostEmbedViewCopyWith<$Res>(_self.embedView!, (value) {
return _then(_self.copyWith(embedView: value));
});
}/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPostCopyWith<$Res>? get threadedPost {
if (_self.threadedPost == null) {
return null;
@@ -243,10 +257,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, SnPostEmbedView? embedView, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int awardedScore, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnPost() when $default != null:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.embedView,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.awardedScore,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return orElse();
}
@@ -264,10 +278,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, SnPostEmbedView? embedView, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int awardedScore, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
switch (_that) {
case _SnPost():
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.embedView,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.awardedScore,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -281,10 +295,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, SnPostEmbedView? embedView, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int awardedScore, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
switch (_that) {
case _SnPost() when $default != null:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.slug,_that.type,_that.meta,_that.embedView,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.awardedScore,_that.pinMode,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.realmId,_that.realm,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
return null;
}
@@ -296,7 +310,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
@JsonSerializable()
class _SnPost implements SnPost {
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.slug, this.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.pinMode, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, this.realmId, this.realm, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
const _SnPost({required this.id, this.title, this.description, this.language, this.editedAt, this.publishedAt = null, this.visibility = 0, this.content, this.slug, this.type = 0, final Map<String, dynamic>? meta, this.embedView, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.awardedScore = 0, this.pinMode, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, this.realmId, this.realm, final List<SnCloudFile> attachments = const [], required this.publisher, final Map<String, int> reactionsCount = const {}, final Map<String, bool> reactionsMade = const {}, final List<dynamic> reactions = const [], final List<SnPostTag> tags = const [], final List<SnPostCategory> categories = const [], final List<dynamic> collections = const [], this.createdAt = null, this.updatedAt = null, this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactionsMade = reactionsMade,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
@override final String id;
@@ -318,11 +332,13 @@ class _SnPost implements SnPost {
return EqualUnmodifiableMapView(value);
}
@override final SnPostEmbedView? embedView;
@override@JsonKey() final int viewsUnique;
@override@JsonKey() final int viewsTotal;
@override@JsonKey() final int upvotes;
@override@JsonKey() final int downvotes;
@override@JsonKey() final int repliesCount;
@override@JsonKey() final int awardedScore;
@override final int? pinMode;
@override final String? threadedPostId;
@override final SnPost? threadedPost;
@@ -400,16 +416,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.embedView, embedView) || other.embedView == embedView)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.awardedScore, awardedScore) || other.awardedScore == awardedScore)&&(identical(other.pinMode, pinMode) || other.pinMode == pinMode)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,slug,type,const DeepCollectionEquality().hash(_meta),embedView,viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,awardedScore,pinMode,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,realmId,realm,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
@override
String toString() {
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, slug: $slug, type: $type, meta: $meta, embedView: $embedView, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, awardedScore: $awardedScore, pinMode: $pinMode, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, realmId: $realmId, realm: $realm, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
}
@@ -420,11 +436,11 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
@override @useResult
$Res call({
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, String? slug, int type, Map<String, dynamic>? meta, SnPostEmbedView? embedView, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, int awardedScore, int? pinMode, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, String? realmId, SnRealm? realm, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<SnPostTag> tags, List<SnPostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
});
@override $SnPostCopyWith<$Res>? get threadedPost;@override $SnPostCopyWith<$Res>? get repliedPost;@override $SnPostCopyWith<$Res>? get forwardedPost;@override $SnRealmCopyWith<$Res>? get realm;@override $SnPublisherCopyWith<$Res> get publisher;
@override $SnPostEmbedViewCopyWith<$Res>? get embedView;@override $SnPostCopyWith<$Res>? get threadedPost;@override $SnPostCopyWith<$Res>? get repliedPost;@override $SnPostCopyWith<$Res>? get forwardedPost;@override $SnRealmCopyWith<$Res>? get realm;@override $SnPublisherCopyWith<$Res> get publisher;
}
/// @nodoc
@@ -437,7 +453,7 @@ class __$SnPostCopyWithImpl<$Res>
/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? slug = freezed,Object? type = null,Object? meta = freezed,Object? embedView = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? awardedScore = null,Object? pinMode = freezed,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? realmId = freezed,Object? realm = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
return _then(_SnPost(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@@ -450,11 +466,13 @@ as int,content: freezed == content ? _self.content : content // ignore: cast_nul
as String?,slug: freezed == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as int,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,embedView: freezed == embedView ? _self.embedView : embedView // ignore: cast_nullable_to_non_nullable
as SnPostEmbedView?,viewsUnique: null == viewsUnique ? _self.viewsUnique : viewsUnique // ignore: cast_nullable_to_non_nullable
as int,viewsTotal: null == viewsTotal ? _self.viewsTotal : viewsTotal // ignore: cast_nullable_to_non_nullable
as int,upvotes: null == upvotes ? _self.upvotes : upvotes // ignore: cast_nullable_to_non_nullable
as int,downvotes: null == downvotes ? _self.downvotes : downvotes // ignore: cast_nullable_to_non_nullable
as int,repliesCount: null == repliesCount ? _self.repliesCount : repliesCount // ignore: cast_nullable_to_non_nullable
as int,awardedScore: null == awardedScore ? _self.awardedScore : awardedScore // ignore: cast_nullable_to_non_nullable
as int,pinMode: freezed == pinMode ? _self.pinMode : pinMode // ignore: cast_nullable_to_non_nullable
as int?,threadedPostId: freezed == threadedPostId ? _self.threadedPostId : threadedPostId // ignore: cast_nullable_to_non_nullable
as String?,threadedPost: freezed == threadedPost ? _self.threadedPost : threadedPost // ignore: cast_nullable_to_non_nullable
@@ -484,6 +502,18 @@ as bool,
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPostEmbedViewCopyWith<$Res>? get embedView {
if (_self.embedView == null) {
return null;
}
return $SnPostEmbedViewCopyWith<$Res>(_self.embedView!, (value) {
return _then(_self.copyWith(embedView: value));
});
}/// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPostCopyWith<$Res>? get threadedPost {
if (_self.threadedPost == null) {
return null;
@@ -1324,6 +1354,269 @@ as int,
}
}
/// @nodoc
mixin _$SnPostEmbedView {
String get uri; double? get aspectRatio; PostEmbedViewRenderer get renderer;
/// Create a copy of SnPostEmbedView
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnPostEmbedViewCopyWith<SnPostEmbedView> get copyWith => _$SnPostEmbedViewCopyWithImpl<SnPostEmbedView>(this as SnPostEmbedView, _$identity);
/// Serializes this SnPostEmbedView to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostEmbedView&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.aspectRatio, aspectRatio) || other.aspectRatio == aspectRatio)&&(identical(other.renderer, renderer) || other.renderer == renderer));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,uri,aspectRatio,renderer);
@override
String toString() {
return 'SnPostEmbedView(uri: $uri, aspectRatio: $aspectRatio, renderer: $renderer)';
}
}
/// @nodoc
abstract mixin class $SnPostEmbedViewCopyWith<$Res> {
factory $SnPostEmbedViewCopyWith(SnPostEmbedView value, $Res Function(SnPostEmbedView) _then) = _$SnPostEmbedViewCopyWithImpl;
@useResult
$Res call({
String uri, double? aspectRatio, PostEmbedViewRenderer renderer
});
}
/// @nodoc
class _$SnPostEmbedViewCopyWithImpl<$Res>
implements $SnPostEmbedViewCopyWith<$Res> {
_$SnPostEmbedViewCopyWithImpl(this._self, this._then);
final SnPostEmbedView _self;
final $Res Function(SnPostEmbedView) _then;
/// Create a copy of SnPostEmbedView
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? uri = null,Object? aspectRatio = freezed,Object? renderer = null,}) {
return _then(_self.copyWith(
uri: null == uri ? _self.uri : uri // ignore: cast_nullable_to_non_nullable
as String,aspectRatio: freezed == aspectRatio ? _self.aspectRatio : aspectRatio // ignore: cast_nullable_to_non_nullable
as double?,renderer: null == renderer ? _self.renderer : renderer // ignore: cast_nullable_to_non_nullable
as PostEmbedViewRenderer,
));
}
}
/// Adds pattern-matching-related methods to [SnPostEmbedView].
extension SnPostEmbedViewPatterns on SnPostEmbedView {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostEmbedView value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnPostEmbedView() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostEmbedView value) $default,){
final _that = this;
switch (_that) {
case _SnPostEmbedView():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostEmbedView value)? $default,){
final _that = this;
switch (_that) {
case _SnPostEmbedView() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String uri, double? aspectRatio, PostEmbedViewRenderer renderer)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnPostEmbedView() when $default != null:
return $default(_that.uri,_that.aspectRatio,_that.renderer);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String uri, double? aspectRatio, PostEmbedViewRenderer renderer) $default,) {final _that = this;
switch (_that) {
case _SnPostEmbedView():
return $default(_that.uri,_that.aspectRatio,_that.renderer);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String uri, double? aspectRatio, PostEmbedViewRenderer renderer)? $default,) {final _that = this;
switch (_that) {
case _SnPostEmbedView() when $default != null:
return $default(_that.uri,_that.aspectRatio,_that.renderer);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnPostEmbedView implements SnPostEmbedView {
const _SnPostEmbedView({required this.uri, this.aspectRatio, this.renderer = PostEmbedViewRenderer.webView});
factory _SnPostEmbedView.fromJson(Map<String, dynamic> json) => _$SnPostEmbedViewFromJson(json);
@override final String uri;
@override final double? aspectRatio;
@override@JsonKey() final PostEmbedViewRenderer renderer;
/// Create a copy of SnPostEmbedView
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnPostEmbedViewCopyWith<_SnPostEmbedView> get copyWith => __$SnPostEmbedViewCopyWithImpl<_SnPostEmbedView>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnPostEmbedViewToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostEmbedView&&(identical(other.uri, uri) || other.uri == uri)&&(identical(other.aspectRatio, aspectRatio) || other.aspectRatio == aspectRatio)&&(identical(other.renderer, renderer) || other.renderer == renderer));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,uri,aspectRatio,renderer);
@override
String toString() {
return 'SnPostEmbedView(uri: $uri, aspectRatio: $aspectRatio, renderer: $renderer)';
}
}
/// @nodoc
abstract mixin class _$SnPostEmbedViewCopyWith<$Res> implements $SnPostEmbedViewCopyWith<$Res> {
factory _$SnPostEmbedViewCopyWith(_SnPostEmbedView value, $Res Function(_SnPostEmbedView) _then) = __$SnPostEmbedViewCopyWithImpl;
@override @useResult
$Res call({
String uri, double? aspectRatio, PostEmbedViewRenderer renderer
});
}
/// @nodoc
class __$SnPostEmbedViewCopyWithImpl<$Res>
implements _$SnPostEmbedViewCopyWith<$Res> {
__$SnPostEmbedViewCopyWithImpl(this._self, this._then);
final _SnPostEmbedView _self;
final $Res Function(_SnPostEmbedView) _then;
/// Create a copy of SnPostEmbedView
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? uri = null,Object? aspectRatio = freezed,Object? renderer = null,}) {
return _then(_SnPostEmbedView(
uri: null == uri ? _self.uri : uri // ignore: cast_nullable_to_non_nullable
as String,aspectRatio: freezed == aspectRatio ? _self.aspectRatio : aspectRatio // ignore: cast_nullable_to_non_nullable
as double?,renderer: null == renderer ? _self.renderer : renderer // ignore: cast_nullable_to_non_nullable
as PostEmbedViewRenderer,
));
}
}
// dart format on

View File

@@ -24,11 +24,18 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
slug: json['slug'] as String?,
type: (json['type'] as num?)?.toInt() ?? 0,
meta: json['meta'] as Map<String, dynamic>?,
embedView:
json['embed_view'] == null
? null
: SnPostEmbedView.fromJson(
json['embed_view'] as Map<String, dynamic>,
),
viewsUnique: (json['views_unique'] as num?)?.toInt() ?? 0,
viewsTotal: (json['views_total'] as num?)?.toInt() ?? 0,
upvotes: (json['upvotes'] as num?)?.toInt() ?? 0,
downvotes: (json['downvotes'] as num?)?.toInt() ?? 0,
repliesCount: (json['replies_count'] as num?)?.toInt() ?? 0,
awardedScore: (json['awarded_score'] as num?)?.toInt() ?? 0,
pinMode: (json['pin_mode'] as num?)?.toInt(),
threadedPostId: json['threaded_post_id'] as String?,
threadedPost:
@@ -105,11 +112,13 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
'slug': instance.slug,
'type': instance.type,
'meta': instance.meta,
'embed_view': instance.embedView?.toJson(),
'views_unique': instance.viewsUnique,
'views_total': instance.viewsTotal,
'upvotes': instance.upvotes,
'downvotes': instance.downvotes,
'replies_count': instance.repliesCount,
'awarded_score': instance.awardedScore,
'pin_mode': instance.pinMode,
'threaded_post_id': instance.threadedPostId,
'threaded_post': instance.threadedPost?.toJson(),
@@ -166,3 +175,24 @@ Map<String, dynamic> _$SnSubscriptionStatusToJson(
'publisher_id': instance.publisherId,
'publisher_name': instance.publisherName,
};
_SnPostEmbedView _$SnPostEmbedViewFromJson(Map<String, dynamic> json) =>
_SnPostEmbedView(
uri: json['uri'] as String,
aspectRatio: (json['aspect_ratio'] as num?)?.toDouble(),
renderer:
$enumDecodeNullable(
_$PostEmbedViewRendererEnumMap,
json['renderer'],
) ??
PostEmbedViewRenderer.webView,
);
Map<String, dynamic> _$SnPostEmbedViewToJson(_SnPostEmbedView instance) =>
<String, dynamic>{
'uri': instance.uri,
'aspect_ratio': instance.aspectRatio,
'renderer': _$PostEmbedViewRendererEnumMap[instance.renderer]!,
};
const _$PostEmbedViewRendererEnumMap = {PostEmbedViewRenderer.webView: 0};

599
lib/pods/activity_rpc.dart Normal file
View File

@@ -0,0 +1,599 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:io';
import 'dart:typed_data';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_web_socket/shelf_web_socket.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:path/path.dart' as path;
const String kRpcLogPrefix = 'arRPC.websocket';
const String kRpcIpcLogPrefix = 'arRPC.ipc';
// IPC Constants
const String kIpcBasePath = 'discord-ipc';
// IPC Packet Types
class IpcTypes {
static const int handshake = 0;
static const int frame = 1;
static const int close = 2;
static const int ping = 3;
static const int pong = 4;
}
// IPC Close Codes
class IpcCloseCodes {
static const int closeNormal = 1000;
static const int closeUnsupported = 1003;
static const int closeAbnormal = 1006;
}
// IPC Error Codes
class IpcErrorCodes {
static const int invalidClientId = 4000;
static const int invalidOrigin = 4001;
static const int rateLimited = 4002;
static const int tokenRevoked = 4003;
static const int invalidVersion = 4004;
static const int invalidEncoding = 4005;
}
class ActivityRpcServer {
static const List<int> portRange = [6463, 6472]; // Ports 64636472
Map<String, Function>
handlers; // {connection: (socket), message: (socket, data), close: (socket)}
HttpServer? _httpServer;
ServerSocket? _ipcServer;
final List<WebSocketChannel> _wsSockets = [];
final List<_IpcSocketWrapper> _ipcSockets = [];
ActivityRpcServer(this.handlers);
void updateHandlers(Map<String, Function> newHandlers) {
handlers = newHandlers;
}
// Encode IPC packet
static Uint8List encodeIpcPacket(int type, Map<String, dynamic> data) {
final jsonData = jsonEncode(data);
final dataBytes = utf8.encode(jsonData);
final dataSize = dataBytes.length;
final buffer = ByteData(8 + dataSize);
buffer.setInt32(0, type, Endian.little);
buffer.setInt32(4, dataSize, Endian.little);
buffer.buffer.asUint8List().setRange(8, 8 + dataSize, dataBytes);
return buffer.buffer.asUint8List();
}
Future<String> _getMacOsSystemTmpDir() async {
final result = await Process.run('getconf', ['DARWIN_USER_TEMP_DIR']);
return (result.stdout as String).trim();
}
// Find available IPC socket path
Future<String> _findAvailableIpcPath() async {
// Build list of directories to try, with macOS-specific handling
final baseDirs = <String>[];
if (Platform.isMacOS) {
try {
final macTempDir = await _getMacOsSystemTmpDir();
if (macTempDir.isNotEmpty) {
baseDirs.add(macTempDir);
}
} catch (e) {
developer.log(
'Failed to get macOS system temp dir: $e',
name: kRpcIpcLogPrefix,
);
}
}
// Add other standard directories
final otherDirs = [
Platform.environment['XDG_RUNTIME_DIR'], // User runtime directory
Platform.environment['TMPDIR'], // App container temp (fallback)
Platform.environment['TMP'],
Platform.environment['TEMP'],
'/tmp', // System temp directory - most compatible
];
baseDirs.addAll(
otherDirs.where((dir) => dir != null && dir.isNotEmpty).cast<String>(),
);
for (final baseDir in baseDirs) {
for (int i = 0; i < 10; i++) {
final socketPath = path.join(baseDir, '$kIpcBasePath-$i');
try {
final socket = await ServerSocket.bind(
InternetAddress(socketPath, type: InternetAddressType.unix),
0,
);
socket.close();
// Clean up the test socket
try {
await File(socketPath).delete();
} catch (_) {}
developer.log(
'IPC socket will be created at: $socketPath',
name: kRpcIpcLogPrefix,
);
return socketPath;
} catch (e) {
// Path not available, try next
if (i == 0) {
// Log only for the first attempt per directory
developer.log(
'IPC path $socketPath not available: $e',
name: kRpcIpcLogPrefix,
);
}
continue;
}
}
}
throw Exception(
'No available IPC socket paths found in any temp directory',
);
}
// Start the WebSocket server
Future<void> start() async {
int port = portRange[0];
bool wsSuccess = false;
// Start WebSocket server
while (port <= portRange[1]) {
developer.log('trying port $port', name: kRpcLogPrefix);
try {
// Start HTTP server
_httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
developer.log('listening on $port', name: kRpcLogPrefix);
// Handle WebSocket upgrades
shelf_io.serveRequests(_httpServer!, (Request request) async {
developer.log('new request', name: kRpcLogPrefix);
if (request.headers['upgrade']?.toLowerCase() == 'websocket') {
final handler = webSocketHandler((WebSocketChannel channel) {
_wsSockets.add(channel);
_onWsConnection(channel, request);
});
return handler(request);
}
developer.log(
'new request disposed due to not websocket',
name: kRpcLogPrefix,
);
return Response.notFound('Not a WebSocket request');
});
wsSuccess = true;
break;
} catch (e) {
if (e is SocketException && e.osError?.errorCode == 98) {
// EADDRINUSE
developer.log('$port in use!', name: kRpcLogPrefix);
} else {
developer.log('http error: $e', name: kRpcLogPrefix);
}
port++;
}
}
if (!wsSuccess) {
throw Exception(
'Failed to bind to any port in range ${portRange[0]}${portRange[1]}',
);
}
// Start IPC server (skip on macOS due to sandboxing)
final shouldStartIpc = !Platform.isMacOS;
if (shouldStartIpc) {
try {
final ipcPath = await _findAvailableIpcPath();
_ipcServer = await ServerSocket.bind(
InternetAddress(ipcPath, type: InternetAddressType.unix),
0,
);
developer.log('IPC listening at $ipcPath', name: kRpcIpcLogPrefix);
_ipcServer!.listen((Socket socket) {
_onIpcConnection(socket);
});
} catch (e) {
developer.log('IPC server error: $e', name: kRpcIpcLogPrefix);
// Continue without IPC if it fails
}
} else {
developer.log(
'IPC server disabled on macOS in production mode due to sandboxing',
name: kRpcIpcLogPrefix,
);
}
}
// Stop the server
Future<void> stop() async {
// Stop WebSocket server
for (var socket in _wsSockets) {
await socket.sink.close();
}
_wsSockets.clear();
await _httpServer?.close();
// Stop IPC server
for (var socket in _ipcSockets) {
socket.close();
}
_ipcSockets.clear();
await _ipcServer?.close();
developer.log('servers stopped', name: kRpcLogPrefix);
}
// Handle new WebSocket connection
void _onWsConnection(WebSocketChannel socket, Request request) {
// Parse query parameters
final uri = request.url;
final params = uri.queryParameters;
final ver = int.tryParse(params['v'] ?? '1') ?? 1;
final encoding = params['encoding'] ?? 'json';
final clientId = params['client_id'] ?? '';
final origin = request.headers['origin'] ?? '';
developer.log(
'new WS connection! origin: $origin, params: $params',
name: kRpcLogPrefix,
);
// Validate origin
if (origin.isNotEmpty &&
![
'https://discord.com',
'https://ptb.discord.com',
'https://canary.discord.com',
].contains(origin)) {
developer.log('disallowed origin: $origin', name: kRpcLogPrefix);
socket.sink.close();
return;
}
// Validate encoding
if (encoding != 'json') {
developer.log(
'unsupported encoding requested: $encoding',
name: kRpcLogPrefix,
);
socket.sink.close();
return;
}
// Validate version
if (ver != 1) {
developer.log('unsupported version requested: $ver', name: kRpcLogPrefix);
socket.sink.close();
return;
}
// Store client info on socket
final socketWithMeta = _WsSocketWrapper(socket, clientId, encoding);
// Set up event listeners
socket.stream.listen(
(data) => _onWsMessage(socketWithMeta, data),
onError: (e) {
developer.log('WS socket error: $e', name: kRpcLogPrefix);
},
onDone: () {
developer.log('WS socket closed', name: kRpcLogPrefix);
handlers['close']?.call(socketWithMeta);
_wsSockets.remove(socket);
},
);
// Notify handler of new connection
handlers['connection']?.call(socketWithMeta);
}
// Handle new IPC connection
void _onIpcConnection(Socket socket) {
developer.log('new IPC connection!', name: kRpcIpcLogPrefix);
final socketWrapper = _IpcSocketWrapper(socket);
_ipcSockets.add(socketWrapper);
// Set up event listeners
socket.listen(
(data) => _onIpcData(socketWrapper, data),
onError: (e) {
developer.log('IPC socket error: $e', name: kRpcIpcLogPrefix);
socket.close();
},
onDone: () {
developer.log('IPC socket closed', name: kRpcIpcLogPrefix);
handlers['close']?.call(socketWrapper);
_ipcSockets.remove(socketWrapper);
},
);
}
// Handle incoming WebSocket message
void _onWsMessage(_WsSocketWrapper socket, dynamic data) {
try {
final jsonData = jsonDecode(data as String);
developer.log('WS message: $jsonData', name: kRpcLogPrefix);
handlers['message']?.call(socket, jsonData);
} catch (e) {
developer.log('WS message parse error: $e', name: kRpcLogPrefix);
}
}
// Handle incoming IPC data
void _onIpcData(_IpcSocketWrapper socket, List<int> data) {
try {
socket.addData(data);
final packets = socket.readPackets();
for (final packet in packets) {
_handleIpcPacket(socket, packet);
}
} catch (e) {
developer.log('IPC data error: $e', name: kRpcIpcLogPrefix);
socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString());
}
}
// Handle IPC packet
void _handleIpcPacket(_IpcSocketWrapper socket, _IpcPacket packet) {
switch (packet.type) {
case IpcTypes.ping:
developer.log('IPC ping received', name: kRpcIpcLogPrefix);
socket.sendPong(packet.data);
break;
case IpcTypes.pong:
developer.log('IPC pong received', name: kRpcIpcLogPrefix);
// Handle pong if needed
break;
case IpcTypes.handshake:
if (socket.handshook) {
throw Exception('Already handshook');
}
socket.handshook = true;
_onIpcHandshake(socket, packet.data);
break;
case IpcTypes.frame:
if (!socket.handshook) {
throw Exception('Need to handshake first');
}
developer.log('IPC frame: ${packet.data}', name: kRpcIpcLogPrefix);
handlers['message']?.call(socket, packet.data);
break;
case IpcTypes.close:
socket.close();
break;
default:
throw Exception('Invalid packet type: ${packet.type}');
}
}
// Handle IPC handshake
void _onIpcHandshake(_IpcSocketWrapper socket, Map<String, dynamic> params) {
developer.log('IPC handshake: $params', name: kRpcIpcLogPrefix);
final ver = int.tryParse(params['v']?.toString() ?? '1') ?? 1;
final clientId = params['client_id']?.toString() ?? '';
// Validate version
if (ver != 1) {
developer.log(
'IPC unsupported version requested: $ver',
name: kRpcIpcLogPrefix,
);
socket.closeWithCode(IpcErrorCodes.invalidVersion);
return;
}
// Validate client ID
if (clientId.isEmpty) {
developer.log('IPC client ID required', name: kRpcIpcLogPrefix);
socket.closeWithCode(IpcErrorCodes.invalidClientId);
return;
}
socket.clientId = clientId;
// Notify handler of new connection
handlers['connection']?.call(socket);
}
}
// WebSocket wrapper
class _WsSocketWrapper {
final WebSocketChannel channel;
final String clientId;
final String encoding;
_WsSocketWrapper(this.channel, this.clientId, this.encoding);
void send(Map<String, dynamic> msg) {
developer.log('WS sending: $msg', name: kRpcLogPrefix);
channel.sink.add(jsonEncode(msg));
}
}
// IPC wrapper
class _IpcSocketWrapper {
final Socket socket;
String clientId = '';
bool handshook = false;
final List<int> _buffer = [];
_IpcSocketWrapper(this.socket);
void addData(List<int> data) {
_buffer.addAll(data);
}
void send(Map<String, dynamic> msg) {
developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix);
final packet = ActivityRpcServer.encodeIpcPacket(IpcTypes.frame, msg);
socket.add(packet);
}
void sendPong(dynamic data) {
final packet = ActivityRpcServer.encodeIpcPacket(IpcTypes.pong, data ?? {});
socket.add(packet);
}
void close() {
socket.close();
}
void closeWithCode(int code, [String message = '']) {
final closeData = {'code': code, 'message': message};
final packet = ActivityRpcServer.encodeIpcPacket(IpcTypes.close, closeData);
socket.add(packet);
socket.close();
}
List<_IpcPacket> readPackets() {
final packets = <_IpcPacket>[];
while (_buffer.length >= 8) {
final buffer = Uint8List.fromList(_buffer);
final byteData = ByteData.view(buffer.buffer);
final type = byteData.getInt32(0, Endian.little);
final dataSize = byteData.getInt32(4, Endian.little);
if (_buffer.length < 8 + dataSize) break;
final dataBytes = _buffer.sublist(8, 8 + dataSize);
final jsonStr = utf8.decode(dataBytes);
final jsonData = jsonDecode(jsonStr);
packets.add(_IpcPacket(type, jsonData));
_buffer.removeRange(0, 8 + dataSize);
}
return packets;
}
}
// IPC Packet structure
class _IpcPacket {
final int type;
final Map<String, dynamic> data;
_IpcPacket(this.type, this.data);
}
// State management for server status and activities
class ServerState {
final String status;
final List<String> activities;
ServerState({required this.status, this.activities = const []});
ServerState copyWith({String? status, List<String>? activities}) {
return ServerState(
status: status ?? this.status,
activities: activities ?? this.activities,
);
}
}
class ServerStateNotifier extends StateNotifier<ServerState> {
final ActivityRpcServer server;
ServerStateNotifier(this.server)
: super(ServerState(status: 'Server not started'));
Future<void> start() async {
try {
await server.start();
state = state.copyWith(status: 'Server running');
} catch (e) {
state = state.copyWith(status: 'Server failed: $e');
}
}
void updateStatus(String status) {
state = state.copyWith(status: status);
}
void addActivity(String activity) {
state = state.copyWith(activities: [...state.activities, activity]);
}
}
// Providers
final rpcServerStateProvider =
StateNotifierProvider<ServerStateNotifier, ServerState>((ref) {
final server = ActivityRpcServer({});
final notifier = ServerStateNotifier(server);
server.updateHandlers({
'connection': (socket) {
final clientId =
socket is _WsSocketWrapper
? socket.clientId
: (socket as _IpcSocketWrapper).clientId;
notifier.updateStatus('Client connected (ID: $clientId)');
// Send READY event
socket.send({
'cmd': 'DISPATCH',
'data': {
'v': 1,
'config': {
'cdn_host': 'fake.cdn',
'api_endpoint': '//fake.api',
'environment': 'dev',
},
'user': {
'id': 'fake_user_id',
'username': 'FakeUser',
'discriminator': '0001',
'avatar': null,
'bot': false,
},
},
'evt': 'READY',
'nonce': '12345', // Should be dynamic
});
},
'message': (socket, dynamic data) {
if (data['cmd'] == 'SET_ACTIVITY') {
notifier.addActivity(
'Activity: ${data['args']['activity']['details'] ?? 'Unknown'}',
);
// Echo back success
socket.send({
'cmd': 'SET_ACTIVITY',
'data': data['args']['activity'],
'evt': null,
'nonce': data['nonce'],
});
}
},
'close': (socket) {
notifier.updateStatus('Client disconnected');
},
});
return notifier;
});
final rpcServerProvider = Provider<ActivityRpcServer>((ref) {
final notifier = ref.watch(rpcServerStateProvider.notifier);
return notifier.server;
});

View File

@@ -789,11 +789,8 @@ class LevelingScreen extends HookConsumerWidget {
if (context.mounted) showLoadingModal(context);
if (paidOrder != null) {
await client.post(
'/id/subscriptions/order/handle',
data: {'order_id': paidOrder.id},
);
// Wait for server to handle order
await Future.delayed(const Duration(seconds: 1));
ref.invalidate(accountStellarSubscriptionProvider);
ref.read(userInfoProvider.notifier).fetchUser();
if (context.mounted) {

View File

@@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/auth.dart';
import 'package:island/models/account.dart';
@@ -479,6 +480,7 @@ class AccountSettingsScreen extends HookConsumerWidget {
);
},
),
const Gap(8),
]
: null,
),

View File

@@ -62,6 +62,32 @@ class ContactMethodSheet extends HookConsumerWidget {
}
}
Future<void> makeContactMethodPublic() async {
try {
showLoadingModal(context);
final client = ref.read(apiClientProvider);
await client.post('/id/accounts/me/contacts/${contact.id}/public');
if (context.mounted) Navigator.pop(context, true);
} catch (err) {
showErrorAlert(err);
} finally {
if (context.mounted) hideLoadingModal(context);
}
}
Future<void> makeContactMethodPrivate() async {
try {
showLoadingModal(context);
final client = ref.read(apiClientProvider);
await client.delete('/id/accounts/me/contacts/${contact.id}/public');
if (context.mounted) Navigator.pop(context, true);
} catch (err) {
showErrorAlert(err);
} finally {
if (context.mounted) hideLoadingModal(context);
}
}
return SheetScaffold(
titleText: 'contactMethod'.tr(),
child: Column(
@@ -111,6 +137,27 @@ class ContactMethodSheet extends HookConsumerWidget {
backgroundColor: Theme.of(context).colorScheme.tertiary,
),
),
if (contact.isPublic)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Badge(
label: Text('contactMethodPublic'.tr()),
textColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
),
if (!contact.isPublic)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Badge(
label: Text('contactMethodPrivate'.tr()),
textColor: Theme.of(context).colorScheme.onSurface,
backgroundColor:
Theme.of(
context,
).colorScheme.surfaceContainerHighest,
),
),
],
),
],
@@ -130,6 +177,20 @@ class ContactMethodSheet extends HookConsumerWidget {
onTap: setContactMethodAsPrimary,
contentPadding: EdgeInsets.symmetric(horizontal: 20),
),
if (contact.verifiedAt != null && !contact.isPublic)
ListTile(
leading: const Icon(Symbols.public),
title: Text('contactMethodMakePublic').tr(),
onTap: makeContactMethodPublic,
contentPadding: EdgeInsets.symmetric(horizontal: 20),
),
if (contact.verifiedAt != null && contact.isPublic)
ListTile(
leading: const Icon(Symbols.visibility_off),
title: Text('contactMethodMakePrivate').tr(),
onTap: makeContactMethodPrivate,
contentPadding: EdgeInsets.symmetric(horizontal: 20),
),
ListTile(
leading: const Icon(Symbols.delete),
title: Text('contactMethodDelete').tr(),

View File

@@ -321,6 +321,59 @@ class _AccountProfileLinks extends StatelessWidget {
}
}
class _AccountProfileContacts extends StatelessWidget {
final SnAccount data;
const _AccountProfileContacts({required this.data});
@override
Widget build(BuildContext context) {
final publicContacts = data.contacts.where((c) => c.isPublic).toList();
if (publicContacts.isEmpty) return const SizedBox.shrink();
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'contactMethod',
).tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
for (final contact in publicContacts)
ListTile(
title: Text(contact.content),
subtitle: Text(switch (contact.type) {
0 => 'contactMethodTypeEmail'.tr(),
1 => 'contactMethodTypePhone'.tr(),
_ => 'contactMethodTypeAddress'.tr(),
}),
leading: Icon(switch (contact.type) {
0 => Symbols.mail,
1 => Symbols.phone,
_ => Symbols.home,
}),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Symbols.chevron_right),
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
onTap: () {
switch (contact.type) {
case 0:
launchUrlString('mailto:${contact.content}');
case 1:
launchUrlString('tel:${contact.content}');
default:
// For address, maybe copy to clipboard or do nothing
Clipboard.setData(ClipboardData(text: contact.content));
}
},
),
],
),
);
}
}
class _AccountPublisherList extends StatelessWidget {
final List<SnPublisher> publishers;
@@ -792,6 +845,10 @@ class AccountProfileScreen extends HookConsumerWidget {
SliverToBoxAdapter(
child: _AccountProfileLinks(data: data),
),
if (data.contacts.any((c) => c.isPublic))
SliverToBoxAdapter(
child: _AccountProfileContacts(data: data),
),
SliverToBoxAdapter(
child: _AccountProfileDetail(data: data),
),
@@ -916,6 +973,12 @@ class AccountProfileScreen extends HookConsumerWidget {
data: data,
).padding(horizontal: 4),
),
if (data.contacts.any((c) => c.isPublic))
SliverToBoxAdapter(
child: _AccountProfileContacts(
data: data,
).padding(horizontal: 4),
),
SliverToBoxAdapter(
child: _AccountPublisherList(
publishers: accountPublishers.value ?? [],

View File

@@ -727,7 +727,11 @@ class _ChatMemberListSheet extends HookConsumerWidget {
children: [
Flexible(child: Text(member.account.nick)),
if (member.status != null)
AccountStatusLabel(status: member.status!),
AccountStatusLabel(
status: member.status!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (member.joinedAt == null)
const Icon(Symbols.pending_actions, size: 20),
],

View File

@@ -128,14 +128,6 @@ class ArticleComposeScreen extends HookConsumerWidget {
return null;
}, []);
// Auto-save cleanup
useEffect(() {
return () {
state.stopAutoSave();
ComposeLogic.dispose(state);
};
}, [state]);
// Helper methods
void showSettingsSheet() {
showModalBottomSheet(
@@ -182,6 +174,12 @@ class ArticleComposeScreen extends HookConsumerWidget {
MarkdownTextContent(
content: contentValue.text,
textStyle: theme.textTheme.bodyMedium,
attachments:
state.attachments.value
.where((e) => e.isOnCloud)
.map((e) => e.data)
.cast<SnCloudFile>()
.toList(),
),
],
);
@@ -268,7 +266,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
child: KeyboardListener(
focusNode: FocusNode(),
onKeyEvent:
(event) => _handleKeyPress(
(event) => ComposeLogic.handleKeyPress(
event,
state,
ref,
@@ -511,38 +509,4 @@ class ArticleComposeScreen extends HookConsumerWidget {
),
);
}
// Helper method to handle keyboard shortcuts
void _handleKeyPress(
KeyEvent event,
ComposeState state,
WidgetRef ref,
BuildContext context, {
SnPost? originalPost,
}) {
if (event is! RawKeyDownEvent) return;
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
final isModifierPressed =
HardwareKeyboard.instance.isMetaPressed ||
HardwareKeyboard.instance.isControlPressed;
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
if (isPaste && isModifierPressed) {
ComposeLogic.handlePaste(state);
} else if (isSave && isModifierPressed) {
ComposeLogic.saveDraft(ref, state);
ComposeLogic.saveDraft(ref, state);
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
ComposeLogic.performAction(
ref,
state,
context,
originalPost: originalPost,
);
}
}
// Helper method to save article draft
}

View File

@@ -77,6 +77,22 @@ class PostActionButtons extends HookConsumerWidget {
final isAuthor =
user.value != null && user.value?.id == post.publisher.accountId;
String formatScore(int score) {
if (score >= 1000000) {
double value = score / 1000000;
return value % 1 == 0
? '${value.toInt()}m'
: '${value.toStringAsFixed(1)}m';
} else if (score >= 1000) {
double value = score / 1000;
return value % 1 == 0
? '${value.toInt()}k'
: '${value.toStringAsFixed(1)}k';
} else {
return score.toString();
}
}
final actions = <Widget>[];
const kButtonHeight = 40.0;
@@ -254,6 +270,17 @@ class PostActionButtons extends HookConsumerWidget {
),
];
actions.add(
FilledButton.tonalIcon(
onPressed: () {},
icon: const Icon(Symbols.star),
label:
post.awardedScore > 0
? Text('${formatScore(post.awardedScore)} pts')
: Text('award').tr(),
),
);
actions.add(
Row(
mainAxisSize: MainAxisSize.min,

View File

@@ -277,8 +277,6 @@ class PostSearchScreen extends HookConsumerWidget {
Text('pinned'.tr()),
],
),
// TODO: Add dropdown for type selection
// TODO: Add multi-select for categories and tags
],
),
),

View File

@@ -668,7 +668,13 @@ class _RealmMemberListSheet extends HookConsumerWidget {
title: Row(
spacing: 6,
children: [
Flexible(child: Text(member.account!.nick)),
Flexible(
child: Text(
member.account!.nick,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (member.status != null)
AccountStatusLabel(status: member.status!),
if (member.joinedAt == null)

View File

@@ -39,8 +39,13 @@ class ComposeStorageNotifier extends _$ComposeStorageNotifier {
await database.addPostDraft(
PostDraftsCompanion(
id: Value(updatedDraft.id),
post: Value(jsonEncode(updatedDraft.toJson())),
title: Value(updatedDraft.title),
description: Value(updatedDraft.description),
content: Value(updatedDraft.content),
visibility: Value(updatedDraft.visibility),
type: Value(updatedDraft.type),
lastModified: Value(updatedDraft.updatedAt ?? DateTime.now()),
postData: Value(jsonEncode(updatedDraft.toJson())),
),
);
} catch (e) {

View File

@@ -7,7 +7,7 @@ part of 'compose_storage_db.dart';
// **************************************************************************
String _$composeStorageNotifierHash() =>
r'4ab4dce85d0a961f096dc3b11505f8f0964dee9d';
r'8baf17aa06b6f69641c20645ba8a3dfe01c97f8c';
/// See also [ComposeStorageNotifier].
@ProviderFor(ComposeStorageNotifier)

View File

@@ -3,6 +3,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/activity_rpc.dart';
import 'package:island/pods/websocket.dart';
import 'package:island/screens/tray_manager.dart';
import 'package:island/services/notify.dart';
@@ -31,7 +32,10 @@ class AppWrapper extends HookConsumerWidget with TrayListener {
TrayService.instance.initialize(this);
ref.read(rpcServerStateProvider.notifier).start();
return () {
ref.read(rpcServerProvider).stop();
TrayService.instance.dispose(this);
sharingService.dispose();
ntySubs?.cancel();

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
@@ -57,11 +58,21 @@ class CheckInWidget extends HookConsumerWidget {
final todayResult = ref.watch(checkInResultTodayProvider);
final nextNotableDay = ref.watch(nextNotableDayProvider);
// Update time every second for live progress
final currentTime = useState(DateTime.now());
useEffect(() {
final timer = Timer.periodic(const Duration(seconds: 1), (_) {
currentTime.value = DateTime.now();
});
return timer.cancel;
}, []);
final now = currentTime.value;
final userinfo = ref.watch(userInfoProvider);
final isAdult = useMemoized(() {
final birthday = userinfo.value?.profile.birthday;
if (birthday == null) return false;
final now = DateTime.now();
final age =
now.year -
birthday.year -
@@ -72,6 +83,12 @@ class CheckInWidget extends HookConsumerWidget {
return age >= 18;
}, [userinfo]);
final progress = (now.hour * 60.0 + now.minute) / (24 * 60);
final endOfDay = DateTime(now.year, now.month, now.day, 23, 59, 59);
final timeLeft = endOfDay.difference(now);
final timeLeftFormatted =
'${timeLeft.inHours.toString().padLeft(2, '0')}:${(timeLeft.inMinutes % 60).toString().padLeft(2, '0')}:${(timeLeft.inSeconds % 60).toString().padLeft(2, '0')}';
Future<void> checkIn({String? captchatTk}) async {
final client = ref.read(apiClientProvider);
try {
@@ -100,106 +117,102 @@ class CheckInWidget extends HookConsumerWidget {
return Card(
margin:
margin ?? EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 8,
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: 8,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
switch (DateTime.now().weekday) {
6 || 7 => Symbols.weekend,
_ => isAdult ? Symbols.work : Symbols.school,
},
fill: 1,
size: 16,
).padding(right: 2),
Text(DateFormat('EEE').format(DateTime.now()))
.fontSize(16)
.bold()
.textColor(
Theme.of(context).colorScheme.onSecondaryContainer,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: 6,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
switch (DateTime.now().weekday) {
6 || 7 => Symbols.weekend,
_ => isAdult ? Symbols.work : Symbols.school,
},
fill: 1,
size: 16,
).padding(right: 2),
Text(
DateFormat('EEE').format(DateTime.now()),
).fontSize(16).bold(),
Text(
DateFormat('MM/dd').format(DateTime.now()),
).fontSize(16),
Tooltip(
message: timeLeftFormatted,
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
value: progress,
strokeWidth: 2,
),
),
Text(DateFormat('MM/dd').format(DateTime.now()))
.fontSize(12)
.textColor(
Theme.of(context).colorScheme.onSecondaryContainer,
)
.padding(top: 2),
],
),
Row(
spacing: 5,
children: [
Text('notableDayNext')
.tr(args: [nextNotableDay.value?.localName ?? 'idk'])
.fontSize(12),
SlideCountdown(
decoration: const BoxDecoration(),
style: const TextStyle(fontSize: 12),
separatorStyle: const TextStyle(fontSize: 12),
padding: EdgeInsets.zero,
duration: nextNotableDay.value?.date.difference(
DateTime.now(),
),
),
],
),
],
).padding(horizontal: 16, top: 8),
const Divider(height: 1),
Row(
children: [
Expanded(
child: AnimatedSwitcher(
],
),
Row(
spacing: 5,
children: [
Text('notableDayNext')
.tr(args: [nextNotableDay.value?.localName ?? 'idk'])
.fontSize(12),
if (nextNotableDay.value != null)
SlideCountdown(
decoration: const BoxDecoration(),
style: const TextStyle(fontSize: 12),
separatorStyle: const TextStyle(fontSize: 12),
padding: EdgeInsets.zero,
duration: nextNotableDay.value?.date.difference(
DateTime.now(),
),
),
],
),
const Gap(2),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: todayResult.when(
data: (result) {
if (result == null) return _CheckInNoneWidget();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'checkInResultLevel${result.level}',
).tr().fontSize(15).bold(),
Wrap(
children:
result.tips
.map((e) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
e.isPositive
? Symbols.thumb_up
: Symbols.thumb_down,
size: 12,
),
const Gap(4),
Text(e.title).fontSize(11),
],
);
})
.toList()
.expand(
(widget) => [
widget,
Text(' · ').fontSize(11),
],
)
.toList()
..removeLast(),
),
],
if (result == null) {
return Text('checkInNoneHint').tr().fontSize(11);
}
return Wrap(
alignment: WrapAlignment.start,
runAlignment: WrapAlignment.start,
children:
result.tips
.map((e) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
e.isPositive
? Symbols.thumb_up
: Symbols.thumb_down,
size: 12,
),
const Gap(4),
Text(e.title).fontSize(11),
],
);
})
.toList()
.expand(
(widget) => [
widget,
Text(' · ').fontSize(11),
],
)
.toList()
..removeLast(),
);
},
loading: () => _CheckInNoneWidget(),
loading: () => Text('checkInNoneHint').tr().fontSize(11),
error:
(err, stack) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -209,9 +222,35 @@ class CheckInWidget extends HookConsumerWidget {
],
),
),
).alignment(Alignment.centerLeft),
],
),
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 3,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: todayResult.when(
data: (result) {
return Text(
result == null
? 'checkInNone'
: 'checkInResultLevel${result.level}',
textAlign: TextAlign.start,
).tr().fontSize(15).bold();
},
loading: () => Text('checkInNone').tr().fontSize(15).bold(),
error: (err, stack) => Text('error').tr().fontSize(15).bold(),
),
),
IconButton.outlined(
iconSize: 16,
visualDensity: const VisualDensity(
horizontal: -3,
vertical: -2,
),
onPressed: () {
if (todayResult.valueOrNull == null) {
checkIn();
@@ -238,24 +277,9 @@ class CheckInWidget extends HookConsumerWidget {
),
),
],
).padding(horizontal: 16, bottom: 12, top: 4),
),
],
),
);
}
}
class _CheckInNoneWidget extends StatelessWidget {
const _CheckInNoneWidget();
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('checkInNone').tr().fontSize(15).bold(),
Text('checkInNoneHint').tr().fontSize(11),
],
).padding(horizontal: 16, vertical: 12),
);
}
}

View File

@@ -6,17 +6,21 @@ import 'package:flutter_platform_alert/flutter_platform_alert.dart';
String _parseRemoteError(DioException err) {
log('${err.requestOptions.method} ${err.requestOptions.uri} ${err.message}');
if (err.response?.data is String) return err.response?.data;
if (err.response?.data?['errors'] != null) {
String? message;
if (err.response?.data is String) {
message = err.response?.data;
} else if (err.response?.data?['errors'] != null) {
final errors = err.response?.data['errors'] as Map<String, dynamic>;
return errors.values
message = errors.values
.map(
(ele) =>
(ele as List<dynamic>).map((ele) => ele.toString()).join('\n'),
)
.join('\n');
}
return err.message ?? err.toString();
if (message == null || message.isEmpty) message = err.response?.statusMessage;
message ??= err.message;
return message ?? err.toString();
}
void showErrorAlert(dynamic err) async {

View File

@@ -830,7 +830,7 @@ class _CloudFileListEntry extends HookConsumerWidget {
bg = ImageFiltered(
imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: CloudFileWidget(
fit: fit,
fit: BoxFit.cover,
item: file,
noBlurhash: true,
useInternalGate: false,

View File

@@ -142,10 +142,6 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
// Set initial mode based on stored PIN and biometric support
if (_hasStoredPin && _hasBiometricSupport && widget.enableBiometric) {
_isPinMode = false;
// Automatically trigger biometric authentication
WidgetsBinding.instance.addPostFrameCallback((_) {
_authenticateWithBiometric();
});
} else {
_isPinMode = true;
}
@@ -270,6 +266,8 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
}
} else if (err.response?.statusCode == 400) {
errorMessage = err.response?.data?['error'] ?? errorMessage;
} else {
rethrow;
}
}
throw errorMessage;
@@ -419,42 +417,48 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
}
Widget _buildBiometricAuth() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.fingerprint, size: 48),
const Gap(16),
Text(
'useBiometricToConfirm'.tr(),
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
Text(
'The biometric data will only be processed on your device',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 11,
),
textAlign: TextAlign.center,
).opacity(0.75),
const Gap(28),
ElevatedButton.icon(
onPressed: _authenticateWithBiometric,
icon: const Icon(Symbols.fingerprint),
label: Text('authenticateNow'.tr()),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
TextButton(
onPressed: () => _fallbackToPinMode(null),
child: Text('usePinInstead'.tr()),
),
],
).center();
return SingleChildScrollView(
child:
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.fingerprint, size: 48),
const Gap(16),
Text(
'useBiometricToConfirm'.tr(),
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
Text(
'The biometric data will only be processed on your device',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 11,
),
textAlign: TextAlign.center,
).opacity(0.75),
const Gap(28),
ElevatedButton.icon(
onPressed: _authenticateWithBiometric,
icon: const Icon(Symbols.fingerprint),
label: Text('authenticateNow'.tr()),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
),
TextButton(
onPressed: () => _fallbackToPinMode(null),
child: Text('usePinInstead'.tr()),
),
],
).center(),
);
}
Widget _buildActionButtons() {

View File

@@ -44,13 +44,13 @@ class PollStatsWidget extends StatelessWidget {
// yes/no: map {true: count, false: count}
if (raw is Map) {
final int yes =
(raw[true] is int)
? raw[true] as int
: int.tryParse('${raw[true]}') ?? 0;
(raw['true'] is int)
? raw['true'] as int
: int.tryParse('${raw['true']}') ?? 0;
final int no =
(raw[false] is int)
? raw[false] as int
: int.tryParse('${raw[false]}') ?? 0;
(raw['false'] is int)
? raw['false'] as int
: int.tryParse('${raw['false']}') ?? 0;
final total = (yes + no).clamp(0, 1 << 31);
final yesPct = total == 0 ? 0.0 : yes / total;
final noPct = total == 0 ? 0.0 : no / total;

View File

@@ -17,6 +17,7 @@ class PollSubmit extends ConsumerStatefulWidget {
this.onCancel,
this.showProgress = true,
this.isReadonly = false,
this.isInitiallyExpanded = false,
});
final SnPollWithStats poll;
@@ -36,6 +37,9 @@ class PollSubmit extends ConsumerStatefulWidget {
final bool isReadonly;
/// Whether the poll should start expanded instead of collapsed.
final bool isInitiallyExpanded;
@override
ConsumerState<PollSubmit> createState() => _PollSubmitState();
}
@@ -45,6 +49,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
int _index = 0;
bool _submitting = false;
bool _isModifying = false; // New state to track if user is modifying answers
bool _isCollapsed = true; // New state to track collapse/expand
/// Collected answers, keyed by questionId
late Map<String, dynamic> _answers;
@@ -65,6 +70,8 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
_questions = [...widget.poll.questions]
..sort((a, b) => a.order.compareTo(b.order));
_answers = Map<String, dynamic>.from(widget.initialAnswers ?? {});
// Set initial collapse state based on the parameter
_isCollapsed = !widget.isInitiallyExpanded;
if (!widget.isReadonly) {
_loadCurrentIntoLocalState();
// If initial answers are provided, set _isModifying to false initially
@@ -405,6 +412,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
});
},
multiSelectionEnabled: false,
emptySelectionAllowed: true,
),
),
],
@@ -652,39 +660,179 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
);
}
Widget _buildCollapsedView(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.poll.title != null)
Text(
widget.poll.title!,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (widget.poll.description != null)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
widget.poll.description!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).textTheme.bodySmall?.color?.withOpacity(0.7),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
)
else
Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
'${_questions.length} question${_questions.length == 1 ? '' : 's'}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).textTheme.bodySmall?.color?.withOpacity(0.7),
),
),
),
],
),
),
IconButton(
icon: Icon(
_isCollapsed ? Icons.expand_more : Icons.expand_less,
size: 20,
),
onPressed: () {
setState(() {
_isCollapsed = !_isCollapsed;
});
},
visualDensity: VisualDensity.compact,
tooltip: _isCollapsed ? 'expandPoll'.tr() : 'collapsePoll'.tr(),
),
],
),
],
);
}
@override
Widget build(BuildContext context) {
if (_questions.isEmpty) {
return const SizedBox.shrink();
}
// If collapsed, show collapsed view for all states
if (_isCollapsed) {
return _buildCollapsedView(context);
}
// If poll is already submitted and not in readonly mode, and not in modification mode, show submitted view
if (widget.initialAnswers != null && !widget.isReadonly && !_isModifying) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [_buildSubmittedView(context), _buildNavBar(context)],
children: [
_buildCollapsedView(context),
const SizedBox(height: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, anim) {
final offset = Tween<Offset>(
begin: const Offset(0, -0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut));
final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut);
return FadeTransition(
opacity: fade,
child: SlideTransition(position: offset, child: child),
);
},
child: Column(
key: const ValueKey('submitted_expanded'),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [_buildSubmittedView(context), _buildNavBar(context)],
),
),
],
);
}
// If poll is in readonly mode, show readonly view
if (widget.isReadonly) {
return _buildReadonlyView(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildCollapsedView(context),
const SizedBox(height: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, anim) {
final offset = Tween<Offset>(
begin: const Offset(0, -0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut));
final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut);
return FadeTransition(
opacity: fade,
child: SlideTransition(position: offset, child: child),
);
},
child: _buildReadonlyView(context),
),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(context),
const SizedBox(height: 12),
_AnimatedStep(
key: ValueKey(_current.id),
_buildCollapsedView(context),
const SizedBox(height: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, anim) {
final offset = Tween<Offset>(
begin: const Offset(0, -0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut));
final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut);
return FadeTransition(
opacity: fade,
child: SlideTransition(position: offset, child: child),
);
},
child: Column(
key: const ValueKey('normal_expanded'),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [_buildBody(context), _buildStats(context, _current)],
children: [
_buildHeader(context),
const SizedBox(height: 12),
_AnimatedStep(
key: ValueKey(_current.id),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildBody(context),
_buildStats(context, _current),
],
),
),
const SizedBox(height: 16),
_buildNavBar(context),
],
),
),
const SizedBox(height: 16),
_buildNavBar(context),
],
);
}

View File

@@ -0,0 +1,287 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/post/compose_shared.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';
class ComposeEmbedSheet extends HookConsumerWidget {
final ComposeState state;
const ComposeEmbedSheet({super.key, required this.state});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
// Listen to embed view changes
final currentEmbedView = useValueListenable(state.embedView);
// Form state
final uriController = useTextEditingController();
final aspectRatioController = useTextEditingController();
final selectedRenderer = useState<PostEmbedViewRenderer>(
PostEmbedViewRenderer.webView,
);
void clearForm() {
uriController.clear();
aspectRatioController.clear();
selectedRenderer.value = PostEmbedViewRenderer.webView;
}
// Populate form when embed view changes
useEffect(() {
if (currentEmbedView != null) {
uriController.text = currentEmbedView.uri;
aspectRatioController.text =
currentEmbedView.aspectRatio?.toString() ?? '';
selectedRenderer.value = currentEmbedView.renderer;
} else {
clearForm();
}
return null;
}, [currentEmbedView]);
void saveEmbedView() {
final uri = uriController.text.trim();
if (uri.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('embedUriRequired'.tr())));
return;
}
final aspectRatio =
aspectRatioController.text.trim().isNotEmpty
? double.tryParse(aspectRatioController.text.trim())
: null;
final embedView = SnPostEmbedView(
uri: uri,
aspectRatio: aspectRatio,
renderer: selectedRenderer.value,
);
if (currentEmbedView != null) {
ComposeLogic.updateEmbedView(state, embedView);
} else {
ComposeLogic.setEmbedView(state, embedView);
}
}
return SheetScaffold(
titleText: 'embedView'.tr(),
heightFactor: 0.7,
child: Column(
children: [
// Header with save button when editing
if (currentEmbedView != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Row(
children: [
Expanded(
child: Text(
'editEmbed'.tr(),
style: theme.textTheme.titleMedium,
),
),
TextButton(
onPressed: saveEmbedView,
child: Text('save'.tr()),
),
],
),
),
// Content area
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Form fields
TextField(
controller: uriController,
decoration: InputDecoration(
labelText: 'embedUri'.tr(),
hintText: 'https://example.com',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
keyboardType: TextInputType.url,
),
const Gap(16),
TextField(
controller: aspectRatioController,
decoration: InputDecoration(
labelText: 'aspectRatio'.tr(),
hintText: '16/9 = 1.777',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
keyboardType: TextInputType.numberWithOptions(
decimal: true,
),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')),
],
),
const Gap(16),
DropdownButtonFormField2<PostEmbedViewRenderer>(
value: selectedRenderer.value,
decoration: InputDecoration(
labelText: 'renderer'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
items:
PostEmbedViewRenderer.values.map((renderer) {
return DropdownMenuItem(
value: renderer,
child: Text(renderer.name).tr(),
);
}).toList(),
onChanged: (value) {
if (value != null) {
selectedRenderer.value = value;
}
},
),
// Current embed view display (when exists)
if (currentEmbedView != null) ...[
const Gap(32),
Text(
'currentEmbed'.tr(),
style: theme.textTheme.titleMedium,
).padding(horizontal: 4),
const Gap(8),
Card(
margin: EdgeInsets.zero,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Padding(
padding: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 12,
top: 4,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
currentEmbedView.renderer ==
PostEmbedViewRenderer.webView
? Symbols.web
: Symbols.web,
color: colorScheme.primary,
),
const Gap(12),
Expanded(
child: Text(
currentEmbedView.uri,
style: theme.textTheme.bodyMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const Icon(Symbols.delete),
onPressed: () {
showDialog(
context: context,
builder:
(dialogContext) => AlertDialog(
title: Text('deleteEmbed').tr(),
content:
Text('deleteEmbedConfirm').tr(),
actions: [
TextButton(
onPressed:
() =>
Navigator.of(
dialogContext,
).pop(),
child: Text('cancel'.tr()),
),
TextButton(
onPressed: () {
ComposeLogic.deleteEmbedView(
state,
);
clearForm();
Navigator.of(
dialogContext,
).pop();
},
style: TextButton.styleFrom(
foregroundColor:
colorScheme.error,
),
child: Text('delete').tr(),
),
],
),
);
},
tooltip: 'delete'.tr(),
color: colorScheme.error,
),
],
),
const Gap(12),
Text(
'aspectRatio'.tr(),
style: theme.textTheme.labelMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const Gap(4),
Text(
currentEmbedView.aspectRatio != null
? currentEmbedView.aspectRatio!
.toStringAsFixed(2)
: 'notSet'.tr(),
style: theme.textTheme.bodyMedium,
),
],
),
),
),
] else ...[
// Save button for new embed
const Gap(16),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: saveEmbedView,
icon: const Icon(Symbols.add),
label: Text('addEmbed'.tr()),
),
),
],
],
),
),
),
],
),
);
}
}

View File

@@ -37,6 +37,7 @@ class ComposeState {
final ValueNotifier<List<SnPostCategory>> categories;
StringTagController tagsController;
final ValueNotifier<SnRealm?> realm;
final ValueNotifier<SnPostEmbedView?> embedView;
final String draftId;
int postType;
// Linked poll id for this compose session (nullable)
@@ -56,6 +57,7 @@ class ComposeState {
required this.tagsController,
required this.categories,
required this.realm,
required this.embedView,
required this.draftId,
this.postType = 0,
String? pollId,
@@ -120,6 +122,7 @@ class ComposeLogic {
originalPost?.categories ?? [],
),
realm: ValueNotifier(originalPost?.realm),
embedView: ValueNotifier<SnPostEmbedView?>(originalPost?.embedView),
draftId: id,
postType: postType,
// initialize without poll by default
@@ -151,6 +154,7 @@ class ComposeLogic {
tagsController: tagsController,
categories: ValueNotifier<List<SnPostCategory>>([]),
realm: ValueNotifier(null),
embedView: ValueNotifier<SnPostEmbedView?>(draft.embedView),
draftId: draft.id,
postType: postType,
pollId: null,
@@ -169,10 +173,6 @@ class ComposeLogic {
}
try {
if (state._autoSaveTimer == null) {
return;
}
// Upload any local attachments first
final baseUrl = ref.watch(serverUrlProvider);
final token = await getToken(ref.watch(tokenProvider));
@@ -253,6 +253,7 @@ class ComposeLogic {
tags: [],
categories: [],
collections: [],
embedView: state.embedView.value,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
deletedAt: null,
@@ -279,10 +280,6 @@ class ComposeLogic {
}
try {
if (state._autoSaveTimer == null) {
return;
}
final draft = SnPost(
id: state.draftId,
title: state.titleController.text,
@@ -329,6 +326,7 @@ class ComposeLogic {
tags: [],
categories: [],
collections: [],
embedView: state.embedView.value,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
deletedAt: null,
@@ -577,6 +575,18 @@ class ComposeLogic {
);
}
static void setEmbedView(ComposeState state, SnPostEmbedView embedView) {
state.embedView.value = embedView;
}
static void updateEmbedView(ComposeState state, SnPostEmbedView embedView) {
state.embedView.value = embedView;
}
static void deleteEmbedView(ComposeState state) {
state.embedView.value = null;
}
static Future<void> pickPoll(
WidgetRef ref,
ComposeState state,
@@ -660,6 +670,8 @@ class ComposeLogic {
'categories': state.categories.value.map((e) => e.slug).toList(),
if (state.realm.value != null) 'realm_id': state.realm.value?.id,
if (state.pollId.value != null) 'poll_id': state.pollId.value,
if (state.embedView.value != null)
'embed_view': state.embedView.value!.toJson(),
};
// Send request
@@ -753,6 +765,7 @@ class ComposeLogic {
state.tagsController.dispose();
state.categories.dispose();
state.realm.dispose();
state.embedView.dispose();
state.pollId.dispose();
}
}

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart';
import 'package:island/services/compose_storage_db.dart';
import 'package:island/widgets/post/compose_embed_sheet.dart';
import 'package:island/widgets/post/compose_shared.dart';
import 'package:island/widgets/post/draft_manager.dart';
import 'package:material_symbols_icons/symbols.dart';
@@ -33,13 +34,21 @@ class ComposeToolbar extends HookConsumerWidget {
}
void saveDraft() {
ComposeLogic.saveDraft(ref, state);
ComposeLogic.saveDraftManually(ref, state, context);
}
void pickPoll() {
ComposeLogic.pickPoll(ref, state, context);
}
void showEmbedSheet() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ComposeEmbedSheet(state: state),
);
}
void showDraftManager() {
showModalBottomSheet(
context: context,
@@ -112,6 +121,25 @@ class ComposeToolbar extends HookConsumerWidget {
);
},
),
// Embed button with visual state when embed is present
ListenableBuilder(
listenable: state.embedView,
builder: (context, _) {
return IconButton(
onPressed: showEmbedSheet,
icon: const Icon(Symbols.web),
tooltip: 'embedView'.tr(),
color: colorScheme.primary,
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
state.embedView.value != null
? colorScheme.primary.withOpacity(0.15)
: null,
),
),
);
},
),
const Spacer(),
if (originalPost == null && state.isEmpty)
IconButton(

View File

@@ -16,138 +16,145 @@ class DraftManagerSheet extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final isLoading = useState(true);
final searchController = useTextEditingController();
final searchQuery = useState('');
final drafts = ref.watch(composeStorageNotifierProvider);
// Track loading state based on drafts being loaded
useEffect(() {
// Set loading to false after drafts are loaded
// We consider drafts loaded when the provider has been initialized
Future.microtask(() {
if (isLoading.value) {
isLoading.value = false;
}
});
return null;
}, [drafts]);
// Search functionality
final filteredDrafts = useMemoized(() {
if (searchQuery.value.isEmpty) {
return drafts.values.toList()
..sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
}
final sortedDrafts = useMemoized(
() {
final draftList = drafts.values.toList();
draftList.sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
return draftList;
},
[
drafts.length,
drafts.values.map((e) => e.updatedAt!.millisecondsSinceEpoch).join(),
],
);
final query = searchQuery.value.toLowerCase();
return drafts.values.where((draft) {
return (draft.title?.toLowerCase().contains(query) ?? false) ||
(draft.description?.toLowerCase().contains(query) ?? false) ||
(draft.content?.toLowerCase().contains(query) ?? false);
}).toList()
..sort((a, b) => b.updatedAt!.compareTo(a.updatedAt!));
}, [drafts, searchQuery.value]);
return SheetScaffold(
titleText: 'drafts'.tr(),
child:
isLoading.value
? const Center(child: CircularProgressIndicator())
: Column(
children: [
if (sortedDrafts.isEmpty)
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Symbols.draft,
size: 64,
color: colorScheme.onSurface.withOpacity(0.3),
),
const Gap(16),
Text(
'noDrafts'.tr(),
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
),
)
else
Expanded(
child: ListView.builder(
itemCount: sortedDrafts.length,
itemBuilder: (context, index) {
final draft = sortedDrafts[index];
return _DraftItem(
draft: draft,
onTap: () {
Navigator.of(context).pop();
onDraftSelected?.call(draft.id);
},
onDelete: () async {
await ref
.read(composeStorageNotifierProvider.notifier)
.deleteDraft(draft.id);
},
);
},
),
),
if (sortedDrafts.isNotEmpty) ...[
const Divider(),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () async {
final confirmed = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: Text('clearAllDrafts'.tr()),
content: Text(
'clearAllDraftsConfirm'.tr(),
),
actions: [
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(false),
child: Text('cancel'.tr()),
),
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(true),
child: Text('confirm'.tr()),
),
],
),
);
child: Column(
children: [
// Search bar
Padding(
padding: const EdgeInsets.all(16),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'searchDrafts'.tr(),
prefixIcon: const Icon(Symbols.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onChanged: (value) => searchQuery.value = value,
),
),
if (confirmed == true) {
await ref
.read(
composeStorageNotifierProvider.notifier,
)
.clearAllDrafts();
}
},
icon: const Icon(Symbols.delete_sweep),
label: Text('clearAll'.tr()),
),
),
],
// Drafts list
if (filteredDrafts.isEmpty)
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Symbols.draft,
size: 64,
color: colorScheme.onSurface.withOpacity(0.3),
),
const Gap(16),
Text(
searchQuery.value.isEmpty
? 'noDrafts'.tr()
: 'noSearchResults'.tr(),
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurface.withOpacity(0.6),
),
),
],
),
),
)
else
Expanded(
child: ListView.builder(
itemCount: filteredDrafts.length,
itemBuilder: (context, index) {
final draft = filteredDrafts[index];
return _DraftItem(
draft: draft,
onTap: () {
Navigator.of(context).pop();
onDraftSelected?.call(draft.id);
},
onDelete: () async {
await ref
.read(composeStorageNotifierProvider.notifier)
.deleteDraft(draft.id);
},
);
},
),
),
// Clear all button
if (filteredDrafts.isNotEmpty) ...[
const Divider(),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () async {
final confirmed = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: Text('clearAllDrafts'.tr()),
content: Text('clearAllDraftsConfirm'.tr()),
actions: [
TextButton(
onPressed:
() => Navigator.of(context).pop(false),
child: Text('cancel'.tr()),
),
TextButton(
onPressed:
() => Navigator.of(context).pop(true),
child: Text('confirm'.tr()),
),
],
),
);
if (confirmed == true) {
await ref
.read(composeStorageNotifierProvider.notifier)
.clearAllDrafts();
}
},
icon: const Icon(Symbols.delete_sweep),
label: Text('clearAll'.tr()),
),
),
],
),
),
],
],
),
);
}
}
@@ -169,7 +176,7 @@ class _DraftItem extends StatelessWidget {
final preview =
content.length > 100 ? '${content.substring(0, 100)}...' : content;
final timeAgo = _formatTimeAgo(draft.updatedAt!);
final visibility = _parseVisibility(draft.visibility);
final visibility = _parseVisibility(draft.visibility).tr();
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),

View File

@@ -0,0 +1,291 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:url_launcher/url_launcher.dart';
class EmbedViewRenderer extends HookConsumerWidget {
final SnPostEmbedView embedView;
final double? maxHeight;
final BorderRadius? borderRadius;
const EmbedViewRenderer({
super.key,
required this.embedView,
this.maxHeight = 400,
this.borderRadius,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
// State management for lazy loading
final shouldLoad = useState(false);
final isLoading = useState(false);
return Container(
constraints: BoxConstraints(maxHeight: maxHeight ?? 400),
decoration: BoxDecoration(
borderRadius: borderRadius ?? BorderRadius.circular(12),
border: Border.all(
color: colorScheme.outline.withOpacity(0.3),
width: 1,
),
color: colorScheme.surfaceContainerLowest,
),
child: ClipRRect(
borderRadius: borderRadius ?? BorderRadius.circular(12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header with embed info
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh,
border: Border(
bottom: BorderSide(
color: colorScheme.outline.withOpacity(0.2),
width: 1,
),
),
),
child: Row(
children: [
Icon(
embedView.renderer == PostEmbedViewRenderer.webView
? Symbols.web
: Symbols.web,
size: 16,
color: colorScheme.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
_getUriDisplay(embedView.uri),
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: Icon(
Symbols.open_in_new,
size: 16,
color: colorScheme.onSurfaceVariant,
),
onPressed: () async {
final uri = Uri.parse(embedView.uri);
if (await canLaunchUrl(uri)) {
await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
}
},
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity.compact,
tooltip: 'Open in browser',
),
],
),
),
// WebView content with lazy loading
AspectRatio(
aspectRatio: embedView.aspectRatio ?? 1,
child:
shouldLoad.value
? Stack(
children: [
InAppWebView(
initialUrlRequest: URLRequest(
url: WebUri(embedView.uri),
),
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
mediaPlaybackRequiresUserGesture: false,
allowsInlineMediaPlayback: true,
useShouldOverrideUrlLoading: true,
useOnLoadResource: true,
supportZoom: false,
useWideViewPort: false,
loadWithOverviewMode: true,
builtInZoomControls: false,
displayZoomControls: false,
minimumFontSize: 12,
preferredContentMode:
UserPreferredContentMode.RECOMMENDED,
allowsBackForwardNavigationGestures: false,
allowsLinkPreview: false,
isInspectable: false,
),
onWebViewCreated: (controller) {
// Configure webview settings
controller.addJavaScriptHandler(
handlerName: 'onHeightChanged',
callback: (args) {
// Handle dynamic height changes if needed
},
);
},
onLoadStart: (controller, url) {
isLoading.value = true;
},
onLoadStop: (controller, url) async {
isLoading.value = false;
// Inject CSS to improve mobile display and remove borders
await controller.evaluateJavascript(
source: '''
// Remove unwanted elements
var elements = document.querySelectorAll('nav, header, footer, .ads, .advertisement, .sidebar');
for (var i = 0; i < elements.length; i++) {
elements[i].style.display = 'none';
}
// Remove borders from embedded content (YouTube, Vimeo, etc.)
var iframes = document.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
iframes[i].style.border = 'none';
iframes[i].style.borderRadius = '0';
}
// Remove borders from video elements
var videos = document.querySelectorAll('video');
for (var i = 0; i < videos.length; i++) {
videos[i].style.border = 'none';
videos[i].style.borderRadius = '0';
}
// Remove borders from any element that might have them
var allElements = document.querySelectorAll('*');
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].style.border) {
allElements[i].style.border = 'none';
}
}
// Improve readability
var body = document.body;
body.style.fontSize = '14px';
body.style.lineHeight = '1.4';
body.style.margin = '0';
body.style.padding = '0';
// Handle dynamic content
var observer = new MutationObserver(function(mutations) {
// Remove borders from newly added elements
var newIframes = document.querySelectorAll('iframe');
for (var i = 0; i < newIframes.length; i++) {
if (!newIframes[i].style.border || newIframes[i].style.border !== 'none') {
newIframes[i].style.border = 'none';
newIframes[i].style.borderRadius = '0';
}
}
var newVideos = document.querySelectorAll('video');
for (var i = 0; i < newVideos.length; i++) {
if (!newVideos[i].style.border || newVideos[i].style.border !== 'none') {
newVideos[i].style.border = 'none';
newVideos[i].style.borderRadius = '0';
}
}
window.flutter_inappwebview.callHandler('onHeightChanged', document.body.scrollHeight);
});
observer.observe(document.body, { childList: true, subtree: true });
''',
);
},
onLoadError: (controller, url, code, message) {
isLoading.value = false;
},
onLoadHttpError: (
controller,
url,
statusCode,
description,
) {
isLoading.value = false;
},
shouldOverrideUrlLoading: (
controller,
navigationAction,
) async {
final uri = navigationAction.request.url;
if (uri != null &&
uri.toString() != embedView.uri) {
// Open external links in browser
// You might want to use url_launcher here
return NavigationActionPolicy.CANCEL;
}
return NavigationActionPolicy.ALLOW;
},
onProgressChanged: (controller, progress) {
// Handle progress changes if needed
},
onConsoleMessage: (controller, consoleMessage) {
// Handle console messages for debugging
debugPrint(
'WebView Console: ${consoleMessage.message}',
);
},
),
if (isLoading.value)
Container(
color: colorScheme.surfaceContainerLowest,
child: const Center(
child: CircularProgressIndicator(),
),
),
],
)
: GestureDetector(
onTap: () {
shouldLoad.value = true;
},
child: Container(
color: colorScheme.surfaceContainerLowest,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Symbols.play_arrow,
size: 48,
color: colorScheme.onSurfaceVariant.withOpacity(
0.6,
),
),
const SizedBox(height: 8),
Text(
'Tap to load content',
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant
.withOpacity(0.6),
),
),
],
),
),
),
),
],
),
),
);
}
String _getUriDisplay(String uri) {
try {
final parsedUri = Uri.parse(uri);
return parsedUri.host.isNotEmpty ? parsedUri.host : uri;
} catch (e) {
return uri;
}
}
}

View File

@@ -0,0 +1,287 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart';
import 'package:island/models/wallet.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/payment/payment_overlay.dart';
import 'package:material_symbols_icons/symbols.dart';
class PostAwardSheet extends HookConsumerWidget {
final SnPost post;
const PostAwardSheet({super.key, required this.post});
@override
Widget build(BuildContext context, WidgetRef ref) {
final messageController = useTextEditingController();
final amountController = useTextEditingController();
final selectedAttitude = useState<int>(0); // 0 for positive, 2 for negative
return SheetScaffold(
titleText: 'awardPost'.tr(),
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Post Preview Section
_buildPostPreview(context),
const Gap(20),
// Award Result Explanation
_buildAwardResultExplanation(context),
const Gap(20),
Text(
'awardMessage'.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
const Gap(8),
TextField(
controller: messageController,
maxLines: 3,
decoration: InputDecoration(
hintText: 'awardMessageHint'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
),
),
const Gap(16),
Text(
'awardAttitude'.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
const Gap(8),
SegmentedButton<int>(
segments: [
ButtonSegment<int>(
value: 0,
label: Text('awardAttitudePositive'.tr()),
icon: const Icon(Symbols.thumb_up),
),
ButtonSegment<int>(
value: 2,
label: Text('awardAttitudeNegative'.tr()),
icon: const Icon(Symbols.thumb_down),
),
],
selected: {selectedAttitude.value},
onSelectionChanged: (Set<int> selection) {
selectedAttitude.value = selection.first;
},
),
const Gap(16),
Text(
'awardAmount'.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
const Gap(8),
TextField(
controller: amountController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'awardAmountHint'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
suffixText: 'NSP',
),
),
const Gap(24),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed:
() => _submitAward(
context,
ref,
messageController,
amountController,
selectedAttitude.value,
),
icon: const Icon(Symbols.star),
label: Text('awardSubmit'.tr()),
),
),
],
),
),
);
}
Widget _buildPostPreview(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Symbols.article,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
const Gap(8),
Text(
'awardPostPreview'.tr(),
style: Theme.of(
context,
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
),
],
),
const Gap(8),
Text(
post.content ?? 'awardNoContent'.tr(),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
...[
const Gap(4),
Row(
spacing: 6,
children: [
Text(
'awardByPublisher'.tr(args: ['@${post.publisher.name}']),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
ProfilePictureWidget(file: post.publisher.picture, radius: 8),
],
),
],
],
),
);
}
Widget _buildAwardResultExplanation(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).colorScheme.primary.withOpacity(0.2),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Symbols.info,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
const Gap(8),
Text(
'awardBenefits'.tr(),
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
const Gap(8),
Text(
'awardBenefitsDescription'.tr(),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
Future<void> _submitAward(
BuildContext context,
WidgetRef ref,
TextEditingController messageController,
TextEditingController amountController,
int selectedAttitude,
) async {
// Get values from controllers
final message = messageController.text.trim();
final amountText = amountController.text.trim();
// Validate inputs
if (amountText.isEmpty) {
showSnackBar('awardAmountRequired'.tr());
return;
}
final amount = double.tryParse(amountText);
if (amount == null || amount <= 0) {
showSnackBar('awardAmountInvalid'.tr());
return;
}
if (message.length > 4096) {
showSnackBar('awardMessageTooLong'.tr());
return;
}
try {
showLoadingModal(context);
final client = ref.read(apiClientProvider);
// Send award request
final awardResponse = await client.post(
'/sphere/posts/${post.id}/awards',
data: {
'amount': amount,
'attitude': selectedAttitude,
if (message.isNotEmpty) 'message': message,
},
);
final orderId = awardResponse.data['order_id'] as String;
// Fetch order details
final orderResponse = await client.get('/id/orders/$orderId');
final order = SnWalletOrder.fromJson(orderResponse.data);
if (context.mounted) {
hideLoadingModal(context);
// Show payment overlay
final paidOrder = await PaymentOverlay.show(
context: context,
order: order,
enableBiometric: true,
);
if (paidOrder != null && context.mounted) {
showSnackBar('awardSuccess'.tr());
Navigator.of(context).pop();
}
}
} catch (err) {
if (context.mounted) {
hideLoadingModal(context);
showErrorAlert(err);
}
}
}
}

View File

@@ -18,8 +18,10 @@ import 'package:island/screens/posts/compose.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/markdown.dart';
import 'package:island/widgets/post/post_item_screenshot.dart';
import 'package:island/widgets/post/post_award_sheet.dart';
import 'package:island/widgets/post/post_pin_sheet.dart';
import 'package:island/widgets/post/post_shared.dart';
import 'package:island/widgets/post/embed_view_renderer.dart';
import 'package:island/widgets/safety/abuse_report_helper.dart';
import 'package:island/widgets/share/share_sheet.dart';
import 'package:material_symbols_icons/symbols.dart';
@@ -245,6 +247,18 @@ class PostActionableItem extends HookConsumerWidget {
);
},
),
MenuAction(
title: 'award'.tr(),
image: MenuImage.icon(Symbols.star),
callback: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (context) => PostAwardSheet(post: item),
);
},
),
MenuSeparator(),
MenuAction(
title: 'share'.tr(),
@@ -422,6 +436,7 @@ class PostItem extends HookConsumerWidget {
MarkdownTextContent(
content: translatedText.value!,
isSelectable: isTextSelectable,
attachments: item.attachments,
),
],
)
@@ -536,6 +551,12 @@ class PostItem extends HookConsumerWidget {
translationSection: translationSection,
renderingPadding: renderingPadding,
),
if (item.embedView != null)
EmbedViewRenderer(
embedView: item.embedView!,
maxHeight: 400,
borderRadius: BorderRadius.circular(12),
).padding(horizontal: renderingPadding.horizontal, vertical: 8),
if (isShowReference)
ReferencedPostWidget(item: item, renderingPadding: renderingPadding),
if (item.repliesCount > 0 && isEmbedReply)

View File

@@ -141,6 +141,7 @@ class PostReplyPreview extends HookConsumerWidget {
Expanded(
child: MarkdownTextContent(
content: post.content!,
attachments: post.attachments,
).padding(top: 2),
)
else
@@ -210,6 +211,7 @@ class PostReplyPreview extends HookConsumerWidget {
Expanded(
child: MarkdownTextContent(
content: data.value!.content!,
attachments: data.value!.attachments,
),
)
else
@@ -745,7 +747,10 @@ class PostBody extends ConsumerWidget {
style: Theme.of(context).textTheme.bodyMedium,
)
else
MarkdownTextContent(content: '${item.content!}...'),
MarkdownTextContent(
content: '${item.content!}...',
attachments: item.attachments,
),
],
),
)
@@ -784,6 +789,7 @@ class PostBody extends ConsumerWidget {
? '${item.content!}...'
: item.content ?? '',
isSelectable: isTextSelectable,
attachments: item.attachments,
),
if (translationSection != null) translationSection!,
],
@@ -900,6 +906,7 @@ class PostBody extends ConsumerWidget {
poll: SnPollWithStats.fromJson(embedData['poll']),
onSubmit: (_) {},
isReadonly: !isInteractive,
isInitiallyExpanded: isFullPost,
).padding(horizontal: 16, vertical: 12),
),
_ => Text('Unable show embed: ${embedData['type']}'),

View File

@@ -2206,7 +2206,7 @@ packages:
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
dependency: "direct main"
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
@@ -2214,13 +2214,13 @@ packages:
source: hosted
version: "1.4.2"
shelf_web_socket:
dependency: transitive
dependency: "direct main"
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "2.0.1"
shortid:
dependency: transitive
description:

View File

@@ -145,6 +145,8 @@ dependencies:
flutter_local_notifications: ^19.4.1
wakelock_plus: ^1.3.2
slide_countdown: ^2.0.2
shelf: ^1.4.2
shelf_web_socket: ^2.0.0
dev_dependencies:
flutter_test: