Compare commits
21 Commits
3.2.0+131
...
c4d5ba5c9d
| Author | SHA1 | Date | |
|---|---|---|---|
|
c4d5ba5c9d
|
|||
|
1069669049
|
|||
|
aa648fec62
|
|||
|
541900673a
|
|||
|
265502ffd0
|
|||
|
3bd79350d1
|
|||
|
5294d1fb23
|
|||
|
ec1269dcf1
|
|||
|
edb0a25f34
|
|||
|
7cd10118cc
|
|||
|
fcddc8f345
|
|||
|
1cc34240da
|
|||
|
013f7f02bc
|
|||
|
4e79e4100f
|
|||
|
feda1f067f
|
|||
|
fe0e192a43
|
|||
|
93df294142
|
|||
|
78d65c39f3
|
|||
|
18b0dbd797
|
|||
|
80cc8cbb40
|
|||
|
646e95a9fc
|
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -22,6 +22,7 @@ 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,
|
||||
@@ -105,3 +106,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);
|
||||
}
|
||||
|
||||
@@ -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 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.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,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, 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? 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? 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,7 +78,8 @@ 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
|
||||
@@ -111,6 +112,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 +256,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? 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.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 +277,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? 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.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 +294,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? 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.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 +309,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.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,6 +331,7 @@ 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;
|
||||
@@ -400,16 +414,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.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,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, 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 +434,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? 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 +451,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? 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,7 +464,8 @@ 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
|
||||
@@ -484,6 +499,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 +1351,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
|
||||
|
||||
@@ -24,6 +24,12 @@ _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,
|
||||
@@ -105,6 +111,7 @@ 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,
|
||||
@@ -166,3 +173,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
599
lib/pods/activity_rpc.dart
Normal 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 6463–6472
|
||||
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;
|
||||
});
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 ?? [],
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'compose_storage_db.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$composeStorageNotifierHash() =>
|
||||
r'4ab4dce85d0a961f096dc3b11505f8f0964dee9d';
|
||||
r'8baf17aa06b6f69641c20645ba8a3dfe01c97f8c';
|
||||
|
||||
/// See also [ComposeStorageNotifier].
|
||||
@ProviderFor(ComposeStorageNotifier)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -270,6 +270,8 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
|
||||
}
|
||||
} else if (err.response?.statusCode == 400) {
|
||||
errorMessage = err.response?.data?['error'] ?? errorMessage;
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
throw errorMessage;
|
||||
@@ -419,42 +421,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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
287
lib/widgets/post/compose_embed_sheet.dart
Normal file
287
lib/widgets/post/compose_embed_sheet.dart
Normal 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()),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
291
lib/widgets/post/embed_view_renderer.dart
Normal file
291
lib/widgets/post/embed_view_renderer.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
287
lib/widgets/post/post_award_sheet.dart
Normal file
287
lib/widgets/post/post_award_sheet.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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']}'),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user