Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c5cbf0af37 | ||
|
1a31e22450 | ||
|
8e0c0c6054 | ||
|
f3d1183076 | ||
|
a9f7f0cce0 | ||
|
f2943f8411 | ||
|
808e7dcffa | ||
|
9bed4fa6fb | ||
|
e6255a340b | ||
|
78bf319fb7 | ||
|
36a966d582 | ||
|
f72b268d36 | ||
|
44ef31034e | ||
|
229dc2186f | ||
|
a2f9a1efb4 | ||
|
823e3c5de6 | ||
|
faac7bac35 | ||
|
1fac1bfe02 | ||
|
9394b1d9c8 | ||
|
43dd13bac4 | ||
|
65bc372103 | ||
|
6558854a7a | ||
|
892035ab27 | ||
|
87ae8d2ff4 | ||
|
15c2dbaa0d | ||
|
6b3338b885 | ||
|
bb00b1bc6a | ||
|
5e1a15ada2 | ||
|
9bdf8ba346 | ||
|
204c087f29 | ||
|
1def3e1895 | ||
|
550c74e544 | ||
|
a39565f012 | ||
|
aa9755e6a7 | ||
|
b25e8d661a | ||
|
4b253ac3ec | ||
|
5d1b875d3c | ||
|
e2e103fa67 | ||
|
43c90da4e3 | ||
|
fa210dd98f | ||
|
43d9ca92bf | ||
|
5e592c143f | ||
|
0c59816f26 | ||
|
19c2457895 | ||
|
af8d87857e | ||
|
d05f63a36a | ||
|
e2dc520012 | ||
|
cff9c15e31 | ||
|
f00135c4bf | ||
|
30b8a6c30f | ||
|
b9c4ee31b1 | ||
|
87870af866 | ||
|
b83cb0fb0b | ||
|
7fd1fe34e5 | ||
|
1c18330891 | ||
|
d320879ad0 | ||
|
950150e119 | ||
|
3c4a9767e1 | ||
|
5df2445f3f | ||
|
56543d7b4c | ||
|
4c6fea1242 | ||
|
fff43de9e3 | ||
|
b31a915544 |
@@ -51,6 +51,12 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +64,7 @@ android {
|
||||
dependencies {
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation("com.github.bumptech.glide:glide:4.16.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:5.1.0")
|
||||
}
|
||||
|
||||
flutter {
|
||||
|
@@ -12,7 +12,12 @@
|
||||
"package_name": "dev.solsynth.solian"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDvFNudXYs29uDtcCv6pFR8h5tXBs90FYk"
|
||||
@@ -20,7 +25,20 @@
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "961776991058-963m1qin2vtp8fv693b5fdrab5hmpl89.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "dev.solsynth.solian",
|
||||
"app_store_id": "6499032345"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
android/app/proguard-rules.pro
vendored
Normal file
5
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# JNI Zero initialization (required for WebRTC native method registration)
|
||||
-keep class livekit.org.jni_zero.JniInit {
|
||||
# Keep the init method un-obfuscated for native code callback
|
||||
private static java.lang.Object[] init();
|
||||
}
|
BIN
android/app/src/main/res/drawable/ic_notification.png
Executable file
BIN
android/app/src/main/res/drawable/ic_notification.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
|
||||
|
@@ -18,11 +18,11 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.10.1" apply false
|
||||
id("com.android.application") version "8.12.0" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services") version("4.3.15") apply false
|
||||
// END: FlutterFire Configuration
|
||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||
id("org.jetbrains.kotlin.android") version("2.2.0") apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
@@ -144,6 +144,7 @@
|
||||
"other": "{} attachments"
|
||||
},
|
||||
"edited": "Edited",
|
||||
"editedAt": "Edited at {}",
|
||||
"addVideo": "Add video",
|
||||
"addPhoto": "Add photo",
|
||||
"addAudio": "Add audio",
|
||||
@@ -705,6 +706,7 @@
|
||||
"copyToClipboardTooltip": "Copy to clipboard",
|
||||
"postForwardingTo": "Forwarding to",
|
||||
"postReplyingTo": "Replying to",
|
||||
"postReplyPlaceholder": "Post your reply",
|
||||
"postEditing": "You are editing an existing post",
|
||||
"postArticle": "Article",
|
||||
"aboutDeviceName": "Device Name",
|
||||
@@ -761,5 +763,31 @@
|
||||
"publisher": "Publisher",
|
||||
"publisherHint": "Enter the publisher name",
|
||||
"publisherCannotBeEmpty": "Publisher cannot be empty",
|
||||
"operationFailed": "Operation failed: {}"
|
||||
"operationFailed": "Operation failed: {}",
|
||||
"stickerMarketplace": "Sticker Marketplace",
|
||||
"stickerPackAdded": "Sticker pack added to your collection",
|
||||
"stickerPackRemoved": "Sticker pack removed from your collection",
|
||||
"addPack": "Add Pack",
|
||||
"removePack": "Remove Pack",
|
||||
"browseAndAddStickers": "Browse and add sticker packs",
|
||||
"stickerPack": "Sticker Pack",
|
||||
"postCategoryTechnology": "Technology",
|
||||
"postCategoryTravel": "Travel",
|
||||
"postCategoryFood": "Food",
|
||||
"postCategoryHealth": "Health",
|
||||
"postCategoryScience": "Science",
|
||||
"postCategorySports": "Sports",
|
||||
"postCategoryFinance": "Finance",
|
||||
"postCategoryLife": "Life",
|
||||
"postCategoryArt": "Art",
|
||||
"postCategoryStudy": "Study",
|
||||
"postCategoryGaming": "Gaming",
|
||||
"postCategoryProgramming": "Programming",
|
||||
"postCategoryMusic": "Music",
|
||||
"links": "Links",
|
||||
"addLink": "Add link",
|
||||
"linkKey": "Link Name",
|
||||
"linkValue": "URL",
|
||||
"debugOptions": "Debug Options",
|
||||
"joinedAt": "Joined at {}"
|
||||
}
|
||||
|
@@ -120,6 +120,7 @@
|
||||
"other": "{}个附件"
|
||||
},
|
||||
"edited": "已编辑",
|
||||
"editedAt": "编辑于 {}",
|
||||
"addVideo": "添加视频",
|
||||
"addPhoto": "添加照片",
|
||||
"addFile": "添加文件",
|
||||
|
@@ -1 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:b91d12f2892a5609f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}}
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:android:a8d3f7995b0b8e86f4188b","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"solian-0x001","appId":"1:961776991058:ios:727229d368cc47e1f4188b","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"solian-0x001","configurations":{"android":"1:961776991058:android:a8d3f7995b0b8e86f4188b","ios":"1:961776991058:ios:727229d368cc47e1f4188b","macos":"1:961776991058:ios:727229d368cc47e1f4188b","web":"1:961776991058:web:3a912c0eb14028e5f4188b","windows":"1:961776991058:web:3a912c0eb14028e5f4188b"}}}}}}
|
@@ -73,6 +73,8 @@ PODS:
|
||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- Flutter (1.0.0)
|
||||
- flutter_app_update (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview_ios (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||
@@ -178,25 +180,25 @@ PODS:
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.50.3):
|
||||
- sqlite3/common (= 3.50.3)
|
||||
- sqlite3/common (3.50.3)
|
||||
- sqlite3/dbstatvtab (3.50.3):
|
||||
- sqlite3 (3.50.4):
|
||||
- sqlite3/common (= 3.50.4)
|
||||
- sqlite3/common (3.50.4)
|
||||
- sqlite3/dbstatvtab (3.50.4):
|
||||
- sqlite3/common
|
||||
- sqlite3/fts5 (3.50.3):
|
||||
- sqlite3/fts5 (3.50.4):
|
||||
- sqlite3/common
|
||||
- sqlite3/math (3.50.3):
|
||||
- sqlite3/math (3.50.4):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.50.3):
|
||||
- sqlite3/perf-threadsafe (3.50.4):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.50.3):
|
||||
- sqlite3/rtree (3.50.4):
|
||||
- sqlite3/common
|
||||
- sqlite3/session (3.50.3):
|
||||
- sqlite3/session (3.50.4):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.50.3)
|
||||
- sqlite3 (~> 3.50.4)
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/math
|
||||
@@ -223,6 +225,7 @@ DEPENDENCIES:
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
|
||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
@@ -293,6 +296,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/firebase_messaging/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_app_update:
|
||||
:path: ".symlinks/plugins/flutter_app_update/ios"
|
||||
flutter_inappwebview_ios:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||
flutter_keyboard_visibility:
|
||||
@@ -372,6 +377,7 @@ SPEC CHECKSUMS:
|
||||
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
|
||||
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
@@ -406,8 +412,8 @@ SPEC CHECKSUMS:
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
||||
sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
|
||||
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
|
@@ -2,6 +2,12 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CLIENT_ID</key>
|
||||
<string>961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com</string>
|
||||
<key>REVERSED_CLIENT_ID</key>
|
||||
<string>com.googleusercontent.apps.961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig</string>
|
||||
<key>ANDROID_CLIENT_ID</key>
|
||||
<string>961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com</string>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyCzQIyiYKoYHTpGXhN-IjgMML8z797WVD8</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
|
@@ -34,7 +34,7 @@ class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
||||
}
|
||||
|
||||
let serverUrl = UserDefaults.standard.getServerUrl()
|
||||
let url = "\(serverUrl)/chat/\(metadata["room_id"] ?? "")/messages"
|
||||
let url = "\(serverUrl)/sphere/chat/\(metadata["room_id"] ?? "")/messages"
|
||||
|
||||
let parameters: [String: Any?] = [
|
||||
"content": textResponse.userText,
|
||||
|
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
func getAttachmentUrl(for identifier: String) -> String {
|
||||
let serverBaseUrl = "https://nt.solian.app"
|
||||
let serverBaseUrl = "https://api.solian.app"
|
||||
|
||||
return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/files/\(identifier)"
|
||||
return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/drive/files/\(identifier)"
|
||||
}
|
||||
|
@@ -29,10 +29,7 @@ class DefaultFirebaseOptions {
|
||||
case TargetPlatform.windows:
|
||||
return windows;
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
return windows;
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
@@ -41,13 +38,13 @@ class DefaultFirebaseOptions {
|
||||
}
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBKfIQpTouj5rXnlzkEieSlbAzepm4mgJE',
|
||||
appId: '1:961776991058:web:b91d12f2892a5609f4188b',
|
||||
apiKey: 'AIzaSyCfgOdlcr7h8x8j0WKx_S2wXnGkOopq320',
|
||||
appId: '1:961776991058:web:3a912c0eb14028e5f4188b',
|
||||
messagingSenderId: '961776991058',
|
||||
projectId: 'solian-0x001',
|
||||
authDomain: 'solian-0x001.firebaseapp.com',
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
measurementId: 'G-XY3HHKG0PE',
|
||||
measurementId: 'G-JD1YEG9D6F',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
@@ -64,6 +61,10 @@ class DefaultFirebaseOptions {
|
||||
messagingSenderId: '961776991058',
|
||||
projectId: 'solian-0x001',
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
androidClientId:
|
||||
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
|
||||
iosClientId:
|
||||
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
|
||||
iosBundleId: 'dev.solsynth.solian',
|
||||
);
|
||||
|
||||
@@ -73,6 +74,10 @@ class DefaultFirebaseOptions {
|
||||
messagingSenderId: '961776991058',
|
||||
projectId: 'solian-0x001',
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
androidClientId:
|
||||
'961776991058-r4iv9qoio57ul7utbfpgfrda2etvtch8.apps.googleusercontent.com',
|
||||
iosClientId:
|
||||
'961776991058-stt7et4qvn3cpscl4r61gl1hnlatqkig.apps.googleusercontent.com',
|
||||
iosBundleId: 'dev.solsynth.solian',
|
||||
);
|
||||
|
||||
@@ -85,5 +90,4 @@ class DefaultFirebaseOptions {
|
||||
storageBucket: 'solian-0x001.firebasestorage.app',
|
||||
measurementId: 'G-JD1YEG9D6F',
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -53,10 +53,16 @@ void main() async {
|
||||
try {
|
||||
await langdetect.initLangDetect();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||
|
||||
if (kIsWeb || !Platform.isLinux) {
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
FirebaseMessaging.onBackgroundMessage(
|
||||
_firebaseMessagingBackgroundHandler,
|
||||
);
|
||||
}
|
||||
|
||||
log("[SplashScreen] Firebase is ready!");
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
@@ -165,6 +171,9 @@ class IslandApp extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
useEffect(() {
|
||||
if (!kIsWeb && Platform.isLinux) {
|
||||
return null;
|
||||
}
|
||||
const channel = MethodChannel('dev.solsynth.solian/notifications');
|
||||
|
||||
Future<void> handleInitialLink() async {
|
||||
|
@@ -1,14 +1,26 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
|
||||
part 'developer.freezed.dart';
|
||||
part 'developer.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class SnDeveloper with _$SnDeveloper {
|
||||
const factory SnDeveloper({
|
||||
required String id,
|
||||
required String publisherId,
|
||||
SnPublisher? publisher,
|
||||
}) = _SnDeveloper;
|
||||
|
||||
factory SnDeveloper.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnDeveloperFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class DeveloperStats with _$DeveloperStats {
|
||||
const factory DeveloperStats({
|
||||
@Default(0) int totalCustomApps,
|
||||
}) = _DeveloperStats;
|
||||
const factory DeveloperStats({@Default(0) int totalCustomApps}) =
|
||||
_DeveloperStats;
|
||||
|
||||
factory DeveloperStats.fromJson(Map<String, dynamic> json) =>
|
||||
_$DeveloperStatsFromJson(json);
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,293 @@ part of 'developer.dart';
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnDeveloper {
|
||||
|
||||
String get id; String get publisherId; SnPublisher? get publisher;
|
||||
/// Create a copy of SnDeveloper
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnDeveloperCopyWith<SnDeveloper> get copyWith => _$SnDeveloperCopyWithImpl<SnDeveloper>(this as SnDeveloper, _$identity);
|
||||
|
||||
/// Serializes this SnDeveloper to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnDeveloper&&(identical(other.id, id) || other.id == id)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,publisherId,publisher);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnDeveloper(id: $id, publisherId: $publisherId, publisher: $publisher)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnDeveloperCopyWith<$Res> {
|
||||
factory $SnDeveloperCopyWith(SnDeveloper value, $Res Function(SnDeveloper) _then) = _$SnDeveloperCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String publisherId, SnPublisher? publisher
|
||||
});
|
||||
|
||||
|
||||
$SnPublisherCopyWith<$Res>? get publisher;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnDeveloperCopyWithImpl<$Res>
|
||||
implements $SnDeveloperCopyWith<$Res> {
|
||||
_$SnDeveloperCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnDeveloper _self;
|
||||
final $Res Function(SnDeveloper) _then;
|
||||
|
||||
/// Create a copy of SnDeveloper
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? publisherId = null,Object? publisher = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||
as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||
as SnPublisher?,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnDeveloper
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnPublisherCopyWith<$Res>? get publisher {
|
||||
if (_self.publisher == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) {
|
||||
return _then(_self.copyWith(publisher: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnDeveloper].
|
||||
extension SnDeveloperPatterns on SnDeveloper {
|
||||
/// 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( _SnDeveloper value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnDeveloper() 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( _SnDeveloper value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnDeveloper():
|
||||
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( _SnDeveloper value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnDeveloper() 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 id, String publisherId, SnPublisher? publisher)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnDeveloper() when $default != null:
|
||||
return $default(_that.id,_that.publisherId,_that.publisher);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 id, String publisherId, SnPublisher? publisher) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnDeveloper():
|
||||
return $default(_that.id,_that.publisherId,_that.publisher);}
|
||||
}
|
||||
/// 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 id, String publisherId, SnPublisher? publisher)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnDeveloper() when $default != null:
|
||||
return $default(_that.id,_that.publisherId,_that.publisher);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnDeveloper implements SnDeveloper {
|
||||
const _SnDeveloper({required this.id, required this.publisherId, this.publisher});
|
||||
factory _SnDeveloper.fromJson(Map<String, dynamic> json) => _$SnDeveloperFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String publisherId;
|
||||
@override final SnPublisher? publisher;
|
||||
|
||||
/// Create a copy of SnDeveloper
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnDeveloperCopyWith<_SnDeveloper> get copyWith => __$SnDeveloperCopyWithImpl<_SnDeveloper>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnDeveloperToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnDeveloper&&(identical(other.id, id) || other.id == id)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,publisherId,publisher);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnDeveloper(id: $id, publisherId: $publisherId, publisher: $publisher)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnDeveloperCopyWith<$Res> implements $SnDeveloperCopyWith<$Res> {
|
||||
factory _$SnDeveloperCopyWith(_SnDeveloper value, $Res Function(_SnDeveloper) _then) = __$SnDeveloperCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String publisherId, SnPublisher? publisher
|
||||
});
|
||||
|
||||
|
||||
@override $SnPublisherCopyWith<$Res>? get publisher;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnDeveloperCopyWithImpl<$Res>
|
||||
implements _$SnDeveloperCopyWith<$Res> {
|
||||
__$SnDeveloperCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnDeveloper _self;
|
||||
final $Res Function(_SnDeveloper) _then;
|
||||
|
||||
/// Create a copy of SnDeveloper
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? publisherId = null,Object? publisher = freezed,}) {
|
||||
return _then(_SnDeveloper(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||
as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||
as SnPublisher?,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of SnDeveloper
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnPublisherCopyWith<$Res>? get publisher {
|
||||
if (_self.publisher == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) {
|
||||
return _then(_self.copyWith(publisher: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$DeveloperStats {
|
||||
|
||||
|
@@ -6,6 +6,22 @@ part of 'developer.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_SnDeveloper _$SnDeveloperFromJson(Map<String, dynamic> json) => _SnDeveloper(
|
||||
id: json['id'] as String,
|
||||
publisherId: json['publisher_id'] as String,
|
||||
publisher:
|
||||
json['publisher'] == null
|
||||
? null
|
||||
: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnDeveloperToJson(_SnDeveloper instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'publisher_id': instance.publisherId,
|
||||
'publisher': instance.publisher?.toJson(),
|
||||
};
|
||||
|
||||
_DeveloperStats _$DeveloperStatsFromJson(Map<String, dynamic> json) =>
|
||||
_DeveloperStats(
|
||||
totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0,
|
||||
|
@@ -3,25 +3,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'embed.freezed.dart';
|
||||
part 'embed.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class SnEmbedLink with _$SnEmbedLink {
|
||||
const factory SnEmbedLink({
|
||||
@JsonKey(name: 'Type') required String type,
|
||||
@JsonKey(name: 'Url') required String url,
|
||||
@JsonKey(name: 'Title') required String title,
|
||||
@JsonKey(name: 'Description') required String? description,
|
||||
@JsonKey(name: 'ImageUrl') required String? imageUrl,
|
||||
@JsonKey(name: 'FaviconUrl') @Default("") String faviconUrl,
|
||||
@JsonKey(name: 'SiteName') @Default("") String siteName,
|
||||
@JsonKey(name: 'ContentType') required String? contentType,
|
||||
@JsonKey(name: 'Author') required String? author,
|
||||
@JsonKey(name: 'PublishedDate') required DateTime? publishedDate,
|
||||
}) = _SnEmbedLink;
|
||||
|
||||
factory SnEmbedLink.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnEmbedLinkFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnScrappedLink with _$SnScrappedLink {
|
||||
const factory SnScrappedLink({
|
||||
|
@@ -12,290 +12,6 @@ part of 'embed.dart';
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnEmbedLink {
|
||||
|
||||
@JsonKey(name: 'Type') String get type;@JsonKey(name: 'Url') String get url;@JsonKey(name: 'Title') String get title;@JsonKey(name: 'Description') String? get description;@JsonKey(name: 'ImageUrl') String? get imageUrl;@JsonKey(name: 'FaviconUrl') String get faviconUrl;@JsonKey(name: 'SiteName') String get siteName;@JsonKey(name: 'ContentType') String? get contentType;@JsonKey(name: 'Author') String? get author;@JsonKey(name: 'PublishedDate') DateTime? get publishedDate;
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnEmbedLinkCopyWith<SnEmbedLink> get copyWith => _$SnEmbedLinkCopyWithImpl<SnEmbedLink>(this as SnEmbedLink, _$identity);
|
||||
|
||||
/// Serializes this SnEmbedLink to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnEmbedLinkCopyWith<$Res> {
|
||||
factory $SnEmbedLinkCopyWith(SnEmbedLink value, $Res Function(SnEmbedLink) _then) = _$SnEmbedLinkCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnEmbedLinkCopyWithImpl<$Res>
|
||||
implements $SnEmbedLinkCopyWith<$Res> {
|
||||
_$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnEmbedLink _self;
|
||||
final $Res Function(SnEmbedLink) _then;
|
||||
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnEmbedLink].
|
||||
extension SnEmbedLinkPatterns on SnEmbedLink {
|
||||
/// 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( _SnEmbedLink value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() 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( _SnEmbedLink value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink():
|
||||
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( _SnEmbedLink value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() 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(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() when $default != null:
|
||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);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(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink():
|
||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);}
|
||||
}
|
||||
/// 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(@JsonKey(name: 'Type') String type, @JsonKey(name: 'Url') String url, @JsonKey(name: 'Title') String title, @JsonKey(name: 'Description') String? description, @JsonKey(name: 'ImageUrl') String? imageUrl, @JsonKey(name: 'FaviconUrl') String faviconUrl, @JsonKey(name: 'SiteName') String siteName, @JsonKey(name: 'ContentType') String? contentType, @JsonKey(name: 'Author') String? author, @JsonKey(name: 'PublishedDate') DateTime? publishedDate)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnEmbedLink() when $default != null:
|
||||
return $default(_that.type,_that.url,_that.title,_that.description,_that.imageUrl,_that.faviconUrl,_that.siteName,_that.contentType,_that.author,_that.publishedDate);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnEmbedLink implements SnEmbedLink {
|
||||
const _SnEmbedLink({@JsonKey(name: 'Type') required this.type, @JsonKey(name: 'Url') required this.url, @JsonKey(name: 'Title') required this.title, @JsonKey(name: 'Description') required this.description, @JsonKey(name: 'ImageUrl') required this.imageUrl, @JsonKey(name: 'FaviconUrl') this.faviconUrl = "", @JsonKey(name: 'SiteName') this.siteName = "", @JsonKey(name: 'ContentType') required this.contentType, @JsonKey(name: 'Author') required this.author, @JsonKey(name: 'PublishedDate') required this.publishedDate});
|
||||
factory _SnEmbedLink.fromJson(Map<String, dynamic> json) => _$SnEmbedLinkFromJson(json);
|
||||
|
||||
@override@JsonKey(name: 'Type') final String type;
|
||||
@override@JsonKey(name: 'Url') final String url;
|
||||
@override@JsonKey(name: 'Title') final String title;
|
||||
@override@JsonKey(name: 'Description') final String? description;
|
||||
@override@JsonKey(name: 'ImageUrl') final String? imageUrl;
|
||||
@override@JsonKey(name: 'FaviconUrl') final String faviconUrl;
|
||||
@override@JsonKey(name: 'SiteName') final String siteName;
|
||||
@override@JsonKey(name: 'ContentType') final String? contentType;
|
||||
@override@JsonKey(name: 'Author') final String? author;
|
||||
@override@JsonKey(name: 'PublishedDate') final DateTime? publishedDate;
|
||||
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnEmbedLinkCopyWith<_SnEmbedLink> get copyWith => __$SnEmbedLinkCopyWithImpl<_SnEmbedLink>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnEmbedLinkToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnEmbedLinkCopyWith<$Res> implements $SnEmbedLinkCopyWith<$Res> {
|
||||
factory _$SnEmbedLinkCopyWith(_SnEmbedLink value, $Res Function(_SnEmbedLink) _then) = __$SnEmbedLinkCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnEmbedLinkCopyWithImpl<$Res>
|
||||
implements _$SnEmbedLinkCopyWith<$Res> {
|
||||
__$SnEmbedLinkCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnEmbedLink _self;
|
||||
final $Res Function(_SnEmbedLink) _then;
|
||||
|
||||
/// Create a copy of SnEmbedLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
|
||||
return _then(_SnEmbedLink(
|
||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
|
||||
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
|
||||
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnScrappedLink {
|
||||
|
||||
|
@@ -6,36 +6,6 @@ part of 'embed.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_SnEmbedLink _$SnEmbedLinkFromJson(Map<String, dynamic> json) => _SnEmbedLink(
|
||||
type: json['Type'] as String,
|
||||
url: json['Url'] as String,
|
||||
title: json['Title'] as String,
|
||||
description: json['Description'] as String?,
|
||||
imageUrl: json['ImageUrl'] as String?,
|
||||
faviconUrl: json['FaviconUrl'] as String? ?? "",
|
||||
siteName: json['SiteName'] as String? ?? "",
|
||||
contentType: json['ContentType'] as String?,
|
||||
author: json['Author'] as String?,
|
||||
publishedDate:
|
||||
json['PublishedDate'] == null
|
||||
? null
|
||||
: DateTime.parse(json['PublishedDate'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnEmbedLinkToJson(_SnEmbedLink instance) =>
|
||||
<String, dynamic>{
|
||||
'Type': instance.type,
|
||||
'Url': instance.url,
|
||||
'Title': instance.title,
|
||||
'Description': instance.description,
|
||||
'ImageUrl': instance.imageUrl,
|
||||
'FaviconUrl': instance.faviconUrl,
|
||||
'SiteName': instance.siteName,
|
||||
'ContentType': instance.contentType,
|
||||
'Author': instance.author,
|
||||
'PublishedDate': instance.publishedDate?.toIso8601String(),
|
||||
};
|
||||
|
||||
_SnScrappedLink _$SnScrappedLinkFromJson(Map<String, dynamic> json) =>
|
||||
_SnScrappedLink(
|
||||
type: json['type'] as String,
|
||||
|
@@ -90,3 +90,19 @@ enum SnPollQuestionType {
|
||||
@JsonValue(4)
|
||||
freeText,
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnPollAnswer with _$SnPollAnswer {
|
||||
const factory SnPollAnswer({
|
||||
required String id,
|
||||
required Map<String, dynamic> answer,
|
||||
required String accountId,
|
||||
required String pollId,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
}) = _SnPollAnswer;
|
||||
|
||||
factory SnPollAnswer.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPollAnswerFromJson(json);
|
||||
}
|
||||
|
@@ -1181,6 +1181,287 @@ as int,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnPollAnswer {
|
||||
|
||||
String get id; Map<String, dynamic> get answer; String get accountId; String get pollId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnPollAnswer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnPollAnswerCopyWith<SnPollAnswer> get copyWith => _$SnPollAnswerCopyWithImpl<SnPollAnswer>(this as SnPollAnswer, _$identity);
|
||||
|
||||
/// Serializes this SnPollAnswer to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other.answer, answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(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,const DeepCollectionEquality().hash(answer),accountId,pollId,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnPollAnswerCopyWith<$Res> {
|
||||
factory $SnPollAnswerCopyWith(SnPollAnswer value, $Res Function(SnPollAnswer) _then) = _$SnPollAnswerCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnPollAnswerCopyWithImpl<$Res>
|
||||
implements $SnPollAnswerCopyWith<$Res> {
|
||||
_$SnPollAnswerCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnPollAnswer _self;
|
||||
final $Res Function(SnPollAnswer) _then;
|
||||
|
||||
/// Create a copy of SnPollAnswer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = 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,answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnPollAnswer].
|
||||
extension SnPollAnswerPatterns on SnPollAnswer {
|
||||
/// 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( _SnPollAnswer value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPollAnswer() 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( _SnPollAnswer value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPollAnswer():
|
||||
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( _SnPollAnswer value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPollAnswer() 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 id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPollAnswer() when $default != null:
|
||||
return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);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 id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPollAnswer():
|
||||
return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
}
|
||||
/// 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 id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnPollAnswer() when $default != null:
|
||||
return $default(_that.id,_that.answer,_that.accountId,_that.pollId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnPollAnswer implements SnPollAnswer {
|
||||
const _SnPollAnswer({required this.id, required final Map<String, dynamic> answer, required this.accountId, required this.pollId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _answer = answer;
|
||||
factory _SnPollAnswer.fromJson(Map<String, dynamic> json) => _$SnPollAnswerFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
final Map<String, dynamic> _answer;
|
||||
@override Map<String, dynamic> get answer {
|
||||
if (_answer is EqualUnmodifiableMapView) return _answer;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_answer);
|
||||
}
|
||||
|
||||
@override final String accountId;
|
||||
@override final String pollId;
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
|
||||
/// Create a copy of SnPollAnswer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnPollAnswerCopyWith<_SnPollAnswer> get copyWith => __$SnPollAnswerCopyWithImpl<_SnPollAnswer>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$SnPollAnswerToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPollAnswer&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._answer, _answer)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.pollId, pollId) || other.pollId == pollId)&&(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,const DeepCollectionEquality().hash(_answer),accountId,pollId,createdAt,updatedAt,deletedAt);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPollAnswer(id: $id, answer: $answer, accountId: $accountId, pollId: $pollId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnPollAnswerCopyWith<$Res> implements $SnPollAnswerCopyWith<$Res> {
|
||||
factory _$SnPollAnswerCopyWith(_SnPollAnswer value, $Res Function(_SnPollAnswer) _then) = __$SnPollAnswerCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, Map<String, dynamic> answer, String accountId, String pollId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnPollAnswerCopyWithImpl<$Res>
|
||||
implements _$SnPollAnswerCopyWith<$Res> {
|
||||
__$SnPollAnswerCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnPollAnswer _self;
|
||||
final $Res Function(_SnPollAnswer) _then;
|
||||
|
||||
/// Create a copy of SnPollAnswer
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? answer = null,Object? accountId = null,Object? pollId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnPollAnswer(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,answer: null == answer ? _self._answer : answer // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||
as String,pollId: null == pollId ? _self.pollId : pollId // ignore: cast_nullable_to_non_nullable
|
||||
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
@@ -131,3 +131,28 @@ Map<String, dynamic> _$SnPollOptionToJson(_SnPollOption instance) =>
|
||||
'description': instance.description,
|
||||
'order': instance.order,
|
||||
};
|
||||
|
||||
_SnPollAnswer _$SnPollAnswerFromJson(Map<String, dynamic> json) =>
|
||||
_SnPollAnswer(
|
||||
id: json['id'] as String,
|
||||
answer: json['answer'] as Map<String, dynamic>,
|
||||
accountId: json['account_id'] as String,
|
||||
pollId: json['poll_id'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt:
|
||||
json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnPollAnswerToJson(_SnPollAnswer instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'answer': instance.answer,
|
||||
'account_id': instance.accountId,
|
||||
'poll_id': instance.pollId,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
@@ -36,8 +36,8 @@ sealed class SnPost with _$SnPost {
|
||||
@Default({}) Map<String, int> reactionsCount,
|
||||
@Default({}) Map<String, bool> reactionsMade,
|
||||
@Default([]) List<dynamic> reactions,
|
||||
@Default([]) List<PostTag> tags,
|
||||
@Default([]) List<PostCategory> categories,
|
||||
@Default([]) List<SnPostTag> tags,
|
||||
@Default([]) List<SnPostCategory> categories,
|
||||
@Default([]) List<dynamic> collections,
|
||||
@Default(null) DateTime? createdAt,
|
||||
@Default(null) DateTime? updatedAt,
|
||||
|
@@ -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; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; Map<String, bool> get reactionsMade; List<dynamic> get reactions; List<PostTag> get tags; List<PostCategory> 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; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; 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)
|
||||
@@ -48,7 +48,7 @@ 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, 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
|
||||
});
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@ as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : r
|
||||
as Map<String, int>,reactionsMade: null == reactionsMade ? _self.reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, bool>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
@@ -227,7 +227,7 @@ 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, 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.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_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 _:
|
||||
@@ -248,7 +248,7 @@ 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, 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.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_that.attachments,_that.publisher,_that.reactionsCount,_that.reactionsMade,_that.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
||||
@@ -265,7 +265,7 @@ 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, 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.type,_that.meta,_that.viewsUnique,_that.viewsTotal,_that.upvotes,_that.downvotes,_that.repliesCount,_that.threadedPostId,_that.threadedPost,_that.repliedPostId,_that.repliedPost,_that.forwardedPostId,_that.forwardedPost,_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 _:
|
||||
@@ -280,7 +280,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.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, 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<PostTag> tags = const [], final List<PostCategory> 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.type = 0, final Map<String, dynamic>? meta, this.viewsUnique = 0, this.viewsTotal = 0, this.upvotes = 0, this.downvotes = 0, this.repliesCount = 0, this.threadedPostId, this.threadedPost, this.repliedPostId, this.repliedPost, this.forwardedPostId, this.forwardedPost, 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;
|
||||
@@ -341,15 +341,15 @@ class _SnPost implements SnPost {
|
||||
return EqualUnmodifiableListView(_reactions);
|
||||
}
|
||||
|
||||
final List<PostTag> _tags;
|
||||
@override@JsonKey() List<PostTag> get tags {
|
||||
final List<SnPostTag> _tags;
|
||||
@override@JsonKey() List<SnPostTag> get tags {
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tags);
|
||||
}
|
||||
|
||||
final List<PostCategory> _categories;
|
||||
@override@JsonKey() List<PostCategory> get categories {
|
||||
final List<SnPostCategory> _categories;
|
||||
@override@JsonKey() List<SnPostCategory> get categories {
|
||||
if (_categories is EqualUnmodifiableListView) return _categories;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_categories);
|
||||
@@ -400,7 +400,7 @@ 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, Map<String, bool> reactionsMade, List<dynamic> reactions, List<PostTag> tags, List<PostCategory> 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, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, 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
|
||||
});
|
||||
|
||||
|
||||
@@ -446,8 +446,8 @@ as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount :
|
||||
as Map<String, int>,reactionsMade: null == reactionsMade ? _self._reactionsMade : reactionsMade // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, bool>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<PostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnPostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
|
@@ -62,12 +62,12 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
||||
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
||||
tags:
|
||||
(json['tags'] as List<dynamic>?)
|
||||
?.map((e) => PostTag.fromJson(e as Map<String, dynamic>))
|
||||
?.map((e) => SnPostTag.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
categories:
|
||||
(json['categories'] as List<dynamic>?)
|
||||
?.map((e) => PostCategory.fromJson(e as Map<String, dynamic>))
|
||||
?.map((e) => SnPostCategory.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
collections: json['collections'] as List<dynamic>? ?? const [],
|
||||
|
@@ -1,19 +1,30 @@
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/services/text.dart';
|
||||
|
||||
part 'post_category.freezed.dart';
|
||||
part 'post_category.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class PostCategory with _$PostCategory {
|
||||
const factory PostCategory({
|
||||
sealed class SnPostCategory with _$SnPostCategory {
|
||||
const SnPostCategory._();
|
||||
|
||||
const factory SnPostCategory({
|
||||
required String id,
|
||||
required String slug,
|
||||
String? name,
|
||||
@Default([]) List<SnPost> posts,
|
||||
}) = _PostCategory;
|
||||
}) = _SnPostCategory;
|
||||
|
||||
factory PostCategory.fromJson(Map<String, dynamic> json) =>
|
||||
_$PostCategoryFromJson(json);
|
||||
factory SnPostCategory.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPostCategoryFromJson(json);
|
||||
|
||||
String get categoryDisplayTitle {
|
||||
final capitalizedSlug = slug.capitalizeEachWord();
|
||||
if ('postCategory$capitalizedSlug'.trExists()) {
|
||||
return 'postCategory$capitalizedSlug'.tr();
|
||||
}
|
||||
return name ?? slug;
|
||||
}
|
||||
}
|
||||
|
@@ -13,22 +13,22 @@ part of 'post_category.dart';
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PostCategory {
|
||||
mixin _$SnPostCategory {
|
||||
|
||||
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PostCategoryCopyWith<PostCategory> get copyWith => _$PostCategoryCopyWithImpl<PostCategory>(this as PostCategory, _$identity);
|
||||
$SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWithImpl<SnPostCategory>(this as SnPostCategory, _$identity);
|
||||
|
||||
/// Serializes this PostCategory to a JSON map.
|
||||
/// Serializes this SnPostCategory to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PostCategoryCopyWith<$Res> {
|
||||
factory $PostCategoryCopyWith(PostCategory value, $Res Function(PostCategory) _then) = _$PostCategoryCopyWithImpl;
|
||||
abstract mixin class $SnPostCategoryCopyWith<$Res> {
|
||||
factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -56,14 +56,14 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PostCategoryCopyWithImpl<$Res>
|
||||
implements $PostCategoryCopyWith<$Res> {
|
||||
_$PostCategoryCopyWithImpl(this._self, this._then);
|
||||
class _$SnPostCategoryCopyWithImpl<$Res>
|
||||
implements $SnPostCategoryCopyWith<$Res> {
|
||||
_$SnPostCategoryCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PostCategory _self;
|
||||
final $Res Function(PostCategory) _then;
|
||||
final SnPostCategory _self;
|
||||
final $Res Function(SnPostCategory) _then;
|
||||
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
@@ -78,8 +78,8 @@ as List<SnPost>,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PostCategory].
|
||||
extension PostCategoryPatterns on PostCategory {
|
||||
/// Adds pattern-matching-related methods to [SnPostCategory].
|
||||
extension SnPostCategoryPatterns on SnPostCategory {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -92,10 +92,10 @@ extension PostCategoryPatterns on PostCategory {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostCategory value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostCategory value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -114,10 +114,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostCategory value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostCategory value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory():
|
||||
case _SnPostCategory():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
@@ -132,10 +132,10 @@ return $default(_that);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostCategory value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostCategory value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -155,7 +155,7 @@ return $default(_that);case _:
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -176,7 +176,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory():
|
||||
case _SnPostCategory():
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
@@ -193,7 +193,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostCategory() when $default != null:
|
||||
case _SnPostCategory() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return null;
|
||||
|
||||
@@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _PostCategory implements PostCategory {
|
||||
const _PostCategory({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
||||
factory _PostCategory.fromJson(Map<String, dynamic> json) => _$PostCategoryFromJson(json);
|
||||
class _SnPostCategory extends SnPostCategory {
|
||||
const _SnPostCategory({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts,super._();
|
||||
factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String slug;
|
||||
@@ -220,20 +220,20 @@ class _PostCategory implements PostCategory {
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PostCategoryCopyWith<_PostCategory> get copyWith => __$PostCategoryCopyWithImpl<_PostCategory>(this, _$identity);
|
||||
_$SnPostCategoryCopyWith<_SnPostCategory> get copyWith => __$SnPostCategoryCopyWithImpl<_SnPostCategory>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$PostCategoryToJson(this, );
|
||||
return _$SnPostCategoryToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PostCategoryCopyWith<$Res> implements $PostCategoryCopyWith<$Res> {
|
||||
factory _$PostCategoryCopyWith(_PostCategory value, $Res Function(_PostCategory) _then) = __$PostCategoryCopyWithImpl;
|
||||
abstract mixin class _$SnPostCategoryCopyWith<$Res> implements $SnPostCategoryCopyWith<$Res> {
|
||||
factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -261,17 +261,17 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PostCategoryCopyWithImpl<$Res>
|
||||
implements _$PostCategoryCopyWith<$Res> {
|
||||
__$PostCategoryCopyWithImpl(this._self, this._then);
|
||||
class __$SnPostCategoryCopyWithImpl<$Res>
|
||||
implements _$SnPostCategoryCopyWith<$Res> {
|
||||
__$SnPostCategoryCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PostCategory _self;
|
||||
final $Res Function(_PostCategory) _then;
|
||||
final _SnPostCategory _self;
|
||||
final $Res Function(_SnPostCategory) _then;
|
||||
|
||||
/// Create a copy of PostCategory
|
||||
/// Create a copy of SnPostCategory
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_PostCategory(
|
||||
return _then(_SnPostCategory(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
|
@@ -6,8 +6,8 @@ part of 'post_category.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) =>
|
||||
_PostCategory(
|
||||
_SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) =>
|
||||
_SnPostCategory(
|
||||
id: json['id'] as String,
|
||||
slug: json['slug'] as String,
|
||||
name: json['name'] as String?,
|
||||
@@ -18,7 +18,7 @@ _PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) =>
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PostCategoryToJson(_PostCategory instance) =>
|
||||
Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'slug': instance.slug,
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
|
||||
@@ -6,14 +5,14 @@ part 'post_tag.freezed.dart';
|
||||
part 'post_tag.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class PostTag with _$PostTag {
|
||||
const factory PostTag({
|
||||
sealed class SnPostTag with _$SnPostTag {
|
||||
const factory SnPostTag({
|
||||
required String id,
|
||||
required String slug,
|
||||
String? name,
|
||||
@Default([]) List<SnPost> posts,
|
||||
}) = _PostTag;
|
||||
}) = _SnPostTag;
|
||||
|
||||
factory PostTag.fromJson(Map<String, dynamic> json) =>
|
||||
_$PostTagFromJson(json);
|
||||
factory SnPostTag.fromJson(Map<String, dynamic> json) =>
|
||||
_$SnPostTagFromJson(json);
|
||||
}
|
||||
|
@@ -13,22 +13,22 @@ part of 'post_tag.dart';
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$PostTag {
|
||||
mixin _$SnPostTag {
|
||||
|
||||
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PostTagCopyWith<PostTag> get copyWith => _$PostTagCopyWithImpl<PostTag>(this as PostTag, _$identity);
|
||||
$SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag>(this as SnPostTag, _$identity);
|
||||
|
||||
/// Serializes this PostTag to a JSON map.
|
||||
/// Serializes this SnPostTag to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -37,15 +37,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PostTagCopyWith<$Res> {
|
||||
factory $PostTagCopyWith(PostTag value, $Res Function(PostTag) _then) = _$PostTagCopyWithImpl;
|
||||
abstract mixin class $SnPostTagCopyWith<$Res> {
|
||||
factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -56,14 +56,14 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PostTagCopyWithImpl<$Res>
|
||||
implements $PostTagCopyWith<$Res> {
|
||||
_$PostTagCopyWithImpl(this._self, this._then);
|
||||
class _$SnPostTagCopyWithImpl<$Res>
|
||||
implements $SnPostTagCopyWith<$Res> {
|
||||
_$SnPostTagCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PostTag _self;
|
||||
final $Res Function(PostTag) _then;
|
||||
final SnPostTag _self;
|
||||
final $Res Function(SnPostTag) _then;
|
||||
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
@@ -78,8 +78,8 @@ as List<SnPost>,
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PostTag].
|
||||
extension PostTagPatterns on PostTag {
|
||||
/// Adds pattern-matching-related methods to [SnPostTag].
|
||||
extension SnPostTagPatterns on SnPostTag {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
@@ -92,10 +92,10 @@ extension PostTagPatterns on PostTag {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PostTag value)? $default,{required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPostTag value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -114,10 +114,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PostTag value) $default,){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPostTag value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag():
|
||||
case _SnPostTag():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
@@ -132,10 +132,10 @@ return $default(_that);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PostTag value)? $default,){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPostTag value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -155,7 +155,7 @@ return $default(_that);case _:
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -176,7 +176,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String slug, String? name, List<SnPost> posts) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag():
|
||||
case _SnPostTag():
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
@@ -193,7 +193,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);}
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String slug, String? name, List<SnPost> posts)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PostTag() when $default != null:
|
||||
case _SnPostTag() when $default != null:
|
||||
return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
return null;
|
||||
|
||||
@@ -205,9 +205,9 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _:
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _PostTag implements PostTag {
|
||||
const _PostTag({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
||||
factory _PostTag.fromJson(Map<String, dynamic> json) => _$PostTagFromJson(json);
|
||||
class _SnPostTag implements SnPostTag {
|
||||
const _SnPostTag({required this.id, required this.slug, this.name, final List<SnPost> posts = const []}): _posts = posts;
|
||||
factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@override final String slug;
|
||||
@@ -220,20 +220,20 @@ class _PostTag implements PostTag {
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PostTagCopyWith<_PostTag> get copyWith => __$PostTagCopyWithImpl<_PostTag>(this, _$identity);
|
||||
_$SnPostTagCopyWith<_SnPostTag> get copyWith => __$SnPostTagCopyWithImpl<_SnPostTag>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$PostTagToJson(this, );
|
||||
return _$SnPostTagToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -242,15 +242,15 @@ int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEqu
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PostTagCopyWith<$Res> implements $PostTagCopyWith<$Res> {
|
||||
factory _$PostTagCopyWith(_PostTag value, $Res Function(_PostTag) _then) = __$PostTagCopyWithImpl;
|
||||
abstract mixin class _$SnPostTagCopyWith<$Res> implements $SnPostTagCopyWith<$Res> {
|
||||
factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String slug, String? name, List<SnPost> posts
|
||||
@@ -261,17 +261,17 @@ $Res call({
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PostTagCopyWithImpl<$Res>
|
||||
implements _$PostTagCopyWith<$Res> {
|
||||
__$PostTagCopyWithImpl(this._self, this._then);
|
||||
class __$SnPostTagCopyWithImpl<$Res>
|
||||
implements _$SnPostTagCopyWith<$Res> {
|
||||
__$SnPostTagCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PostTag _self;
|
||||
final $Res Function(_PostTag) _then;
|
||||
final _SnPostTag _self;
|
||||
final $Res Function(_SnPostTag) _then;
|
||||
|
||||
/// Create a copy of PostTag
|
||||
/// Create a copy of SnPostTag
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,}) {
|
||||
return _then(_PostTag(
|
||||
return _then(_SnPostTag(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
|
@@ -6,7 +6,7 @@ part of 'post_tag.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag(
|
||||
_SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag(
|
||||
id: json['id'] as String,
|
||||
slug: json['slug'] as String,
|
||||
name: json['name'] as String?,
|
||||
@@ -17,9 +17,10 @@ _PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag(
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PostTagToJson(_PostTag instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'slug': instance.slug,
|
||||
'name': instance.name,
|
||||
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'slug': instance.slug,
|
||||
'name': instance.name,
|
||||
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
@@ -35,6 +35,7 @@ sealed class SnStickerPack with _$SnStickerPack {
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
@Default([]) List<SnSticker> stickers,
|
||||
}) = _SnStickerPack;
|
||||
|
||||
factory SnStickerPack.fromJson(Map<String, dynamic> json) =>
|
||||
|
@@ -338,7 +338,7 @@ $SnStickerPackCopyWith<$Res>? get pack {
|
||||
/// @nodoc
|
||||
mixin _$SnStickerPack {
|
||||
|
||||
String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
String get id; String get name; String get description; String get prefix; String get publisherId; SnPublisher? get publisher; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnSticker> get stickers;
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -351,16 +351,16 @@ $SnStickerPackCopyWith<SnStickerPack> get copyWith => _$SnStickerPackCopyWithImp
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(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 SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.stickers, stickers));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(stickers));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ abstract mixin class $SnStickerPackCopyWith<$Res> {
|
||||
factory $SnStickerPackCopyWith(SnStickerPack value, $Res Function(SnStickerPack) _then) = _$SnStickerPackCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
|
||||
});
|
||||
|
||||
|
||||
@@ -388,7 +388,7 @@ class _$SnStickerPackCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
|
||||
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
|
||||
@@ -399,7 +399,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
|
||||
as SnPublisher?,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?,
|
||||
as DateTime?,stickers: null == stickers ? _self.stickers : stickers // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnSticker>,
|
||||
));
|
||||
}
|
||||
/// Create a copy of SnStickerPack
|
||||
@@ -493,10 +494,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnStickerPack() when $default != null:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -514,10 +515,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnStickerPack():
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -531,10 +532,10 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnStickerPack() when $default != null:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publisherId,_that.publisher,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.stickers);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -546,7 +547,7 @@ return $default(_that.id,_that.name,_that.description,_that.prefix,_that.publish
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnStickerPack implements SnStickerPack {
|
||||
const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||
const _SnStickerPack({required this.id, required this.name, required this.description, required this.prefix, required this.publisherId, required this.publisher, required this.createdAt, required this.updatedAt, required this.deletedAt, final List<SnSticker> stickers = const []}): _stickers = stickers;
|
||||
factory _SnStickerPack.fromJson(Map<String, dynamic> json) => _$SnStickerPackFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@@ -558,6 +559,13 @@ class _SnStickerPack implements SnStickerPack {
|
||||
@override final DateTime createdAt;
|
||||
@override final DateTime updatedAt;
|
||||
@override final DateTime? deletedAt;
|
||||
final List<SnSticker> _stickers;
|
||||
@override@JsonKey() List<SnSticker> get stickers {
|
||||
if (_stickers is EqualUnmodifiableListView) return _stickers;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_stickers);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -572,16 +580,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(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 _SnStickerPack&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.prefix, prefix) || other.prefix == prefix)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._stickers, _stickers));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt);
|
||||
int get hashCode => Object.hash(runtimeType,id,name,description,prefix,publisherId,publisher,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_stickers));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnStickerPack(id: $id, name: $name, description: $description, prefix: $prefix, publisherId: $publisherId, publisher: $publisher, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, stickers: $stickers)';
|
||||
}
|
||||
|
||||
|
||||
@@ -592,7 +600,7 @@ abstract mixin class _$SnStickerPackCopyWith<$Res> implements $SnStickerPackCopy
|
||||
factory _$SnStickerPackCopyWith(_SnStickerPack value, $Res Function(_SnStickerPack) _then) = __$SnStickerPackCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String name, String description, String prefix, String publisherId, SnPublisher? publisher, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnSticker> stickers
|
||||
});
|
||||
|
||||
|
||||
@@ -609,7 +617,7 @@ class __$SnStickerPackCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnStickerPack
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = null,Object? prefix = null,Object? publisherId = null,Object? publisher = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? stickers = null,}) {
|
||||
return _then(_SnStickerPack(
|
||||
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
|
||||
@@ -620,7 +628,8 @@ as String,publisher: freezed == publisher ? _self.publisher : publisher // ignor
|
||||
as SnPublisher?,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?,
|
||||
as DateTime?,stickers: null == stickers ? _self._stickers : stickers // ignore: cast_nullable_to_non_nullable
|
||||
as List<SnSticker>,
|
||||
));
|
||||
}
|
||||
|
||||
|
@@ -54,6 +54,11 @@ _SnStickerPack _$SnStickerPackFromJson(Map<String, dynamic> json) =>
|
||||
json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
stickers:
|
||||
(json['stickers'] as List<dynamic>?)
|
||||
?.map((e) => SnSticker.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
|
||||
@@ -67,4 +72,5 @@ Map<String, dynamic> _$SnStickerPackToJson(_SnStickerPack instance) =>
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'stickers': instance.stickers.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
@@ -25,6 +25,32 @@ sealed class SnAccount with _$SnAccount {
|
||||
_$SnAccountFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class ProfileLink with _$ProfileLink {
|
||||
const factory ProfileLink({required String name, required String url}) =
|
||||
_ProfileLink;
|
||||
|
||||
factory ProfileLink.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfileLinkFromJson(json);
|
||||
}
|
||||
|
||||
class ProfileLinkConverter
|
||||
implements JsonConverter<List<ProfileLink>, dynamic> {
|
||||
const ProfileLinkConverter();
|
||||
|
||||
@override
|
||||
List<ProfileLink> fromJson(dynamic json) {
|
||||
return json is List<dynamic>
|
||||
? json.map((e) => ProfileLink.fromJson(e)).cast<ProfileLink>().toList()
|
||||
: <ProfileLink>[];
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> toJson(List<ProfileLink> object) {
|
||||
return object.map((e) => e.toJson()).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
sealed class SnAccountProfile with _$SnAccountProfile {
|
||||
const factory SnAccountProfile({
|
||||
@@ -38,6 +64,7 @@ sealed class SnAccountProfile with _$SnAccountProfile {
|
||||
@Default('') String location,
|
||||
@Default('') String timeZone,
|
||||
DateTime? birthday,
|
||||
@ProfileLinkConverter() @Default([]) List<ProfileLink> links,
|
||||
DateTime? lastSeenAt,
|
||||
SnAccountBadge? activeBadge,
|
||||
required int experience,
|
||||
|
@@ -347,10 +347,270 @@ $SnWalletSubscriptionRefCopyWith<$Res>? get perkSubscription {
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProfileLink {
|
||||
|
||||
String get name; String get url;
|
||||
/// Create a copy of ProfileLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProfileLinkCopyWith<ProfileLink> get copyWith => _$ProfileLinkCopyWithImpl<ProfileLink>(this as ProfileLink, _$identity);
|
||||
|
||||
/// Serializes this ProfileLink to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProfileLink&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,url);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfileLink(name: $name, url: $url)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ProfileLinkCopyWith<$Res> {
|
||||
factory $ProfileLinkCopyWith(ProfileLink value, $Res Function(ProfileLink) _then) = _$ProfileLinkCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String name, String url
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ProfileLinkCopyWithImpl<$Res>
|
||||
implements $ProfileLinkCopyWith<$Res> {
|
||||
_$ProfileLinkCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ProfileLink _self;
|
||||
final $Res Function(ProfileLink) _then;
|
||||
|
||||
/// Create a copy of ProfileLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? url = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ProfileLink].
|
||||
extension ProfileLinkPatterns on ProfileLink {
|
||||
/// 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( _ProfileLink value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ProfileLink() 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( _ProfileLink value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ProfileLink():
|
||||
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( _ProfileLink value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ProfileLink() 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 name, String url)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProfileLink() when $default != null:
|
||||
return $default(_that.name,_that.url);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 name, String url) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProfileLink():
|
||||
return $default(_that.name,_that.url);}
|
||||
}
|
||||
/// 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 name, String url)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ProfileLink() when $default != null:
|
||||
return $default(_that.name,_that.url);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _ProfileLink implements ProfileLink {
|
||||
const _ProfileLink({required this.name, required this.url});
|
||||
factory _ProfileLink.fromJson(Map<String, dynamic> json) => _$ProfileLinkFromJson(json);
|
||||
|
||||
@override final String name;
|
||||
@override final String url;
|
||||
|
||||
/// Create a copy of ProfileLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ProfileLinkCopyWith<_ProfileLink> get copyWith => __$ProfileLinkCopyWithImpl<_ProfileLink>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$ProfileLinkToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfileLink&&(identical(other.name, name) || other.name == name)&&(identical(other.url, url) || other.url == url));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,url);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfileLink(name: $name, url: $url)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ProfileLinkCopyWith<$Res> implements $ProfileLinkCopyWith<$Res> {
|
||||
factory _$ProfileLinkCopyWith(_ProfileLink value, $Res Function(_ProfileLink) _then) = __$ProfileLinkCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String name, String url
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ProfileLinkCopyWithImpl<$Res>
|
||||
implements _$ProfileLinkCopyWith<$Res> {
|
||||
__$ProfileLinkCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ProfileLink _self;
|
||||
final $Res Function(_ProfileLink) _then;
|
||||
|
||||
/// Create a copy of ProfileLink
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? url = null,}) {
|
||||
return _then(_ProfileLink(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnAccountProfile {
|
||||
|
||||
String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday;@ProfileLinkConverter() List<ProfileLink> get links; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||
/// Create a copy of SnAccountProfile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -363,16 +623,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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 SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other.links, links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -383,7 +643,7 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> {
|
||||
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -400,7 +660,7 @@ class _$SnAccountProfileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnAccountProfile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,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,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
@@ -412,7 +672,8 @@ as String,pronouns: null == pronouns ? _self.pronouns : pronouns // ignore: cast
|
||||
as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
|
||||
as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
|
||||
as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,links: null == links ? _self.links : links // ignore: cast_nullable_to_non_nullable
|
||||
as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
|
||||
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
|
||||
@@ -553,10 +814,10 @@ return $default(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnAccountProfile() when $default != null:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -574,10 +835,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnAccountProfile():
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
@@ -591,10 +852,10 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, @ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnAccountProfile() when $default != null:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.bio,_that.gender,_that.pronouns,_that.location,_that.timeZone,_that.birthday,_that.links,_that.lastSeenAt,_that.activeBadge,_that.experience,_that.level,_that.levelingProgress,_that.picture,_that.background,_that.verification,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -606,7 +867,7 @@ return $default(_that.id,_that.firstName,_that.middleName,_that.lastName,_that.b
|
||||
@JsonSerializable()
|
||||
|
||||
class _SnAccountProfile implements SnAccountProfile {
|
||||
const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||
const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, @ProfileLinkConverter() final List<ProfileLink> links = const [], this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}): _links = links;
|
||||
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
|
||||
|
||||
@override final String id;
|
||||
@@ -619,6 +880,13 @@ class _SnAccountProfile implements SnAccountProfile {
|
||||
@override@JsonKey() final String location;
|
||||
@override@JsonKey() final String timeZone;
|
||||
@override final DateTime? birthday;
|
||||
final List<ProfileLink> _links;
|
||||
@override@JsonKey()@ProfileLinkConverter() List<ProfileLink> get links {
|
||||
if (_links is EqualUnmodifiableListView) return _links;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_links);
|
||||
}
|
||||
|
||||
@override final DateTime? lastSeenAt;
|
||||
@override final SnAccountBadge? activeBadge;
|
||||
@override final int experience;
|
||||
@@ -644,16 +912,16 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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 _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&const DeepCollectionEquality().equals(other._links, _links)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(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.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,const DeepCollectionEquality().hash(_links),lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, links: $links, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||
}
|
||||
|
||||
|
||||
@@ -664,7 +932,7 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi
|
||||
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday,@ProfileLinkConverter() List<ProfileLink> links, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||
});
|
||||
|
||||
|
||||
@@ -681,7 +949,7 @@ class __$SnAccountProfileCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of SnAccountProfile
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? links = null,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||
return _then(_SnAccountProfile(
|
||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||
@@ -693,7 +961,8 @@ as String,pronouns: null == pronouns ? _self.pronouns : pronouns // ignore: cast
|
||||
as String,location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable
|
||||
as String,timeZone: null == timeZone ? _self.timeZone : timeZone // ignore: cast_nullable_to_non_nullable
|
||||
as String,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,links: null == links ? _self._links : links // ignore: cast_nullable_to_non_nullable
|
||||
as List<ProfileLink>,lastSeenAt: freezed == lastSeenAt ? _self.lastSeenAt : lastSeenAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,activeBadge: freezed == activeBadge ? _self.activeBadge : activeBadge // ignore: cast_nullable_to_non_nullable
|
||||
as SnAccountBadge?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable
|
||||
as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable
|
||||
|
@@ -47,6 +47,12 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) =>
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
_ProfileLink _$ProfileLinkFromJson(Map<String, dynamic> json) =>
|
||||
_ProfileLink(name: json['name'] as String, url: json['url'] as String);
|
||||
|
||||
Map<String, dynamic> _$ProfileLinkToJson(_ProfileLink instance) =>
|
||||
<String, dynamic>{'name': instance.name, 'url': instance.url};
|
||||
|
||||
_SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
|
||||
_SnAccountProfile(
|
||||
id: json['id'] as String,
|
||||
@@ -62,6 +68,10 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
|
||||
json['birthday'] == null
|
||||
? null
|
||||
: DateTime.parse(json['birthday'] as String),
|
||||
links:
|
||||
json['links'] == null
|
||||
? const []
|
||||
: const ProfileLinkConverter().fromJson(json['links']),
|
||||
lastSeenAt:
|
||||
json['last_seen_at'] == null
|
||||
? null
|
||||
@@ -111,6 +121,7 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
|
||||
'location': instance.location,
|
||||
'time_zone': instance.timeZone,
|
||||
'birthday': instance.birthday?.toIso8601String(),
|
||||
'links': const ProfileLinkConverter().toJson(instance.links),
|
||||
'last_seen_at': instance.lastSeenAt?.toIso8601String(),
|
||||
'active_badge': instance.activeBadge?.toJson(),
|
||||
'experience': instance.experience,
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -18,6 +17,8 @@ sealed class WebSocketState with _$WebSocketState {
|
||||
const factory WebSocketState.connected() = _Connected;
|
||||
const factory WebSocketState.connecting() = _Connecting;
|
||||
const factory WebSocketState.disconnected() = _Disconnected;
|
||||
const factory WebSocketState.serverDown() = _ServerDown;
|
||||
const factory WebSocketState.duplicateDevice() = _DuplicateDevice;
|
||||
const factory WebSocketState.error(String message) = _Error;
|
||||
}
|
||||
|
||||
@@ -49,7 +50,7 @@ class WebSocketService {
|
||||
Timer? _heartbeatTimer;
|
||||
|
||||
DateTime? _heartbeatAt;
|
||||
Duration? _heartbeatDelay;
|
||||
Duration? heartbeatDelay;
|
||||
|
||||
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
||||
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
||||
@@ -81,15 +82,20 @@ class WebSocketService {
|
||||
final dataStr =
|
||||
data is Uint8List ? utf8.decode(data) : data.toString();
|
||||
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
|
||||
if (packet.type == 'error.dupe') {
|
||||
_statusStreamController.sink.add(WebSocketState.duplicateDevice());
|
||||
_channel!.sink.close();
|
||||
return;
|
||||
}
|
||||
_streamController.sink.add(packet);
|
||||
log(
|
||||
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
|
||||
);
|
||||
if (packet.type == 'pong' && _heartbeatAt != null) {
|
||||
var now = DateTime.now();
|
||||
_heartbeatDelay = now.difference(_heartbeatAt!);
|
||||
heartbeatDelay = now.difference(_heartbeatAt!);
|
||||
log(
|
||||
"[WebSocket] Server respond last heartbeat for ${_heartbeatDelay!.inMilliseconds} ms",
|
||||
"[WebSocket] Server respond last heartbeat for ${heartbeatDelay!.inMilliseconds} ms",
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@@ -61,13 +61,15 @@ extension WebSocketStatePatterns on WebSocketState {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _Error value)? error,required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( _Connected value)? connected,TResult Function( _Connecting value)? connecting,TResult Function( _Disconnected value)? disconnected,TResult Function( _ServerDown value)? serverDown,TResult Function( _DuplicateDevice value)? duplicateDevice,TResult Function( _Error value)? error,required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected(_that);case _Connecting() when connecting != null:
|
||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||
return disconnected(_that);case _Error() when error != null:
|
||||
return disconnected(_that);case _ServerDown() when serverDown != null:
|
||||
return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice(_that);case _Error() when error != null:
|
||||
return error(_that);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -86,13 +88,15 @@ return error(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _Error value) error,}){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( _Connected value) connected,required TResult Function( _Connecting value) connecting,required TResult Function( _Disconnected value) disconnected,required TResult Function( _ServerDown value) serverDown,required TResult Function( _DuplicateDevice value) duplicateDevice,required TResult Function( _Error value) error,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected():
|
||||
return connected(_that);case _Connecting():
|
||||
return connecting(_that);case _Disconnected():
|
||||
return disconnected(_that);case _Error():
|
||||
return disconnected(_that);case _ServerDown():
|
||||
return serverDown(_that);case _DuplicateDevice():
|
||||
return duplicateDevice(_that);case _Error():
|
||||
return error(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
@@ -107,13 +111,15 @@ return error(_that);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _Error value)? error,}){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( _Connected value)? connected,TResult? Function( _Connecting value)? connecting,TResult? Function( _Disconnected value)? disconnected,TResult? Function( _ServerDown value)? serverDown,TResult? Function( _DuplicateDevice value)? duplicateDevice,TResult? Function( _Error value)? error,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected(_that);case _Connecting() when connecting != null:
|
||||
return connecting(_that);case _Disconnected() when disconnected != null:
|
||||
return disconnected(_that);case _Error() when error != null:
|
||||
return disconnected(_that);case _ServerDown() when serverDown != null:
|
||||
return serverDown(_that);case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice(_that);case _Error() when error != null:
|
||||
return error(_that);case _:
|
||||
return null;
|
||||
|
||||
@@ -131,12 +137,14 @@ return error(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? connected,TResult Function()? connecting,TResult Function()? disconnected,TResult Function()? serverDown,TResult Function()? duplicateDevice,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected();case _Connecting() when connecting != null:
|
||||
return connecting();case _Disconnected() when disconnected != null:
|
||||
return disconnected();case _Error() when error != null:
|
||||
return disconnected();case _ServerDown() when serverDown != null:
|
||||
return serverDown();case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice();case _Error() when error != null:
|
||||
return error(_that.message);case _:
|
||||
return orElse();
|
||||
|
||||
@@ -155,12 +163,14 @@ return error(_that.message);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function( String message) error,}) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() connected,required TResult Function() connecting,required TResult Function() disconnected,required TResult Function() serverDown,required TResult Function() duplicateDevice,required TResult Function( String message) error,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected():
|
||||
return connected();case _Connecting():
|
||||
return connecting();case _Disconnected():
|
||||
return disconnected();case _Error():
|
||||
return disconnected();case _ServerDown():
|
||||
return serverDown();case _DuplicateDevice():
|
||||
return duplicateDevice();case _Error():
|
||||
return error(_that.message);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
@@ -175,12 +185,14 @@ return error(_that.message);}
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function( String message)? error,}) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? connected,TResult? Function()? connecting,TResult? Function()? disconnected,TResult? Function()? serverDown,TResult? Function()? duplicateDevice,TResult? Function( String message)? error,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _Connected() when connected != null:
|
||||
return connected();case _Connecting() when connecting != null:
|
||||
return connecting();case _Disconnected() when disconnected != null:
|
||||
return disconnected();case _Error() when error != null:
|
||||
return disconnected();case _ServerDown() when serverDown != null:
|
||||
return serverDown();case _DuplicateDevice() when duplicateDevice != null:
|
||||
return duplicateDevice();case _Error() when error != null:
|
||||
return error(_that.message);case _:
|
||||
return null;
|
||||
|
||||
@@ -303,6 +315,82 @@ String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ServerDown with DiagnosticableTreeMixin implements WebSocketState {
|
||||
const _ServerDown();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'WebSocketState.serverDown'))
|
||||
;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerDown);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'WebSocketState.serverDown()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _DuplicateDevice with DiagnosticableTreeMixin implements WebSocketState {
|
||||
const _DuplicateDevice();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'WebSocketState.duplicateDevice'))
|
||||
;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DuplicateDevice);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
|
||||
return 'WebSocketState.duplicateDevice()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
|
@@ -7,6 +7,7 @@ import 'package:island/screens/developers/edit_app.dart';
|
||||
import 'package:island/screens/developers/new_app.dart';
|
||||
import 'package:island/screens/developers/hub.dart';
|
||||
import 'package:island/screens/discovery/articles.dart';
|
||||
import 'package:island/screens/posts/post_category_detail.dart';
|
||||
import 'package:island/screens/posts/post_search.dart';
|
||||
import 'package:island/widgets/app_wrapper.dart';
|
||||
import 'package:island/screens/tabs.dart';
|
||||
@@ -28,6 +29,8 @@ import 'package:island/screens/creators/hub.dart';
|
||||
import 'package:island/screens/creators/posts/post_manage_list.dart';
|
||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||
import 'package:island/screens/creators/stickers/pack_detail.dart';
|
||||
import 'package:island/screens/stickers/marketplace.dart';
|
||||
import 'package:island/screens/stickers/pack_detail.dart';
|
||||
import 'package:island/screens/creators/poll/poll_list.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/creators/webfeed/webfeed_list.dart';
|
||||
@@ -320,15 +323,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
builder: (context, state) => const AboutScreen(),
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
name: 'reportDetail',
|
||||
path: '/safety/reports/me/:id',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
return AbuseReportDetailScreen(reportId: id);
|
||||
},
|
||||
),
|
||||
|
||||
// Main tabs with TabsScreen shell
|
||||
ShellRoute(
|
||||
navigatorKey: _tabsShellKey,
|
||||
@@ -355,6 +349,25 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
return PostDetailScreen(id: id);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postCategoryDetail',
|
||||
path: '/posts/categories/:slug',
|
||||
builder: (context, state) {
|
||||
final slug = state.pathParameters['slug']!;
|
||||
return PostCategoryDetailScreen(slug: slug, isCategory: true);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'postTagDetail',
|
||||
path: '/posts/tags/:slug',
|
||||
builder: (context, state) {
|
||||
final slug = state.pathParameters['slug']!;
|
||||
return PostCategoryDetailScreen(
|
||||
slug: slug,
|
||||
isCategory: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
name: 'publisherProfile',
|
||||
path: '/publishers/:name',
|
||||
@@ -451,6 +464,23 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
path: '/account',
|
||||
builder: (context, state) => const AccountScreen(),
|
||||
),
|
||||
// Sticker marketplace (user-facing, no publisher)
|
||||
GoRoute(
|
||||
name: 'stickerMarketplace',
|
||||
path: '/stickers',
|
||||
builder:
|
||||
(context, state) => const MarketplaceStickersScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
name: 'stickerPackDetail',
|
||||
path: ':packId',
|
||||
builder: (context, state) {
|
||||
final packId = state.pathParameters['packId']!;
|
||||
return MarketplaceStickerPackDetailScreen(id: packId);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
name: 'notifications',
|
||||
path: '/account/notifications',
|
||||
@@ -486,6 +516,14 @@ final routerProvider = Provider<GoRouter>((ref) {
|
||||
path: '/safety/reports/me',
|
||||
builder: (context, state) => const AbuseReportListScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'reportDetail',
|
||||
path: '/safety/reports/me/:id',
|
||||
builder: (context, state) {
|
||||
final id = state.pathParameters['id']!;
|
||||
return AbuseReportDetailScreen(reportId: id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
@@ -7,10 +7,12 @@ import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/services/udid.native.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:island/services/update_service.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
@@ -100,196 +102,226 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _errorMessage != null
|
||||
? Center(child: Text(_errorMessage!))
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 24),
|
||||
// App Icon and Name
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: theme.colorScheme.primary.withOpacity(
|
||||
0.1,
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/icons/icon.png',
|
||||
width: 56,
|
||||
height: 56,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_packageInfo.appName,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'aboutScreenVersionInfo'.tr(
|
||||
args: [_packageInfo.version, _packageInfo.buildNumber],
|
||||
),
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// App Info Card
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'aboutScreenAppInfoSectionTitle'.tr(),
|
||||
: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 540),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.info,
|
||||
label: 'aboutScreenPackageNameLabel'.tr(),
|
||||
value: _packageInfo.packageName,
|
||||
),
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.update,
|
||||
label: 'aboutScreenVersionLabel'.tr(),
|
||||
value: _packageInfo.version,
|
||||
),
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.build,
|
||||
label: 'aboutScreenBuildNumberLabel'.tr(),
|
||||
value: _packageInfo.buildNumber,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (_deviceInfo != null) const SizedBox(height: 16),
|
||||
|
||||
if (_deviceInfo != null)
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'Device Information',
|
||||
children: [
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.label,
|
||||
label: 'aboutDeviceName'.tr(),
|
||||
value: _deviceInfo?.data['name'],
|
||||
const SizedBox(height: 24),
|
||||
// App Icon and Name
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: theme.colorScheme.primary
|
||||
.withOpacity(0.1),
|
||||
child: Image.asset(
|
||||
'assets/icons/icon.png',
|
||||
width: 56,
|
||||
height: 56,
|
||||
),
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.fingerprint,
|
||||
label: 'aboutDeviceIdentifier'.tr(),
|
||||
value: _deviceUdid ?? 'N/A',
|
||||
copyable: true,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_packageInfo.appName,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Links Card
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'aboutScreenLinksSectionTitle'.tr(),
|
||||
children: [
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.privacy_tip,
|
||||
title: 'aboutScreenPrivacyPolicyTitle'.tr(),
|
||||
onTap:
|
||||
() => _launchURL(
|
||||
'https://solsynth.dev/terms/privacy-policy',
|
||||
),
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.description,
|
||||
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
||||
onTap:
|
||||
() => _launchURL(
|
||||
'https://solsynth.dev/terms/basic-law',
|
||||
),
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.code,
|
||||
title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
|
||||
onTap: () {
|
||||
showLicensePage(
|
||||
context: context,
|
||||
applicationName: _packageInfo.appName,
|
||||
applicationVersion:
|
||||
'Version ${_packageInfo.version}',
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Developer Info
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'aboutScreenDeveloperSectionTitle'.tr(),
|
||||
children: [
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.email,
|
||||
title: 'aboutScreenContactUsTitle'.tr(),
|
||||
subtitle: 'lily@solsynth.dev',
|
||||
onTap: () => _launchURL('mailto:lily@solsynth.dev'),
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.copyright,
|
||||
title: 'aboutScreenLicenseTitle'.tr(),
|
||||
subtitle: 'aboutScreenLicenseContent'.tr(
|
||||
args: [DateTime.now().year.toString()],
|
||||
Text(
|
||||
'aboutScreenVersionInfo'.tr(
|
||||
args: [
|
||||
_packageInfo.version,
|
||||
_packageInfo.buildNumber,
|
||||
],
|
||||
),
|
||||
onTap:
|
||||
() => _launchURL(
|
||||
'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
|
||||
),
|
||||
),
|
||||
if (kIsWeb || !(Platform.isMacOS || Platform.isIOS))
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.favorite,
|
||||
title: 'donate'.tr(),
|
||||
subtitle: 'donateDescription'.tr(),
|
||||
onTap: () {
|
||||
launchUrlString(
|
||||
'https://afdian.com/@littlesheep',
|
||||
);
|
||||
},
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.textTheme.bodySmall?.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Copyright
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'aboutScreenCopyright'.tr(
|
||||
args: [DateTime.now().year.toString()],
|
||||
// App Info Card
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'aboutScreenAppInfoSectionTitle'.tr(),
|
||||
children: [
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.info,
|
||||
label: 'aboutScreenPackageNameLabel'.tr(),
|
||||
value: _packageInfo.packageName,
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(1),
|
||||
Text(
|
||||
'aboutScreenMadeWith'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
).fontSize(10).opacity(0.8),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.update,
|
||||
label: 'aboutScreenVersionLabel'.tr(),
|
||||
value: _packageInfo.version,
|
||||
),
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.build,
|
||||
label: 'aboutScreenBuildNumberLabel'.tr(),
|
||||
value: _packageInfo.buildNumber,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Gap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
if (_deviceInfo != null) const SizedBox(height: 16),
|
||||
|
||||
if (_deviceInfo != null)
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'Device Information',
|
||||
children: [
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.label,
|
||||
label: 'aboutDeviceName'.tr(),
|
||||
value: _deviceInfo?.data['name'],
|
||||
),
|
||||
_buildInfoItem(
|
||||
context,
|
||||
icon: Symbols.fingerprint,
|
||||
label: 'aboutDeviceIdentifier'.tr(),
|
||||
value: _deviceUdid ?? 'N/A',
|
||||
copyable: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Links Card
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'aboutScreenLinksSectionTitle'.tr(),
|
||||
children: [
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.system_update,
|
||||
title: 'Check for updates',
|
||||
onTap: () async {
|
||||
// Fetch latest release and show the unified sheet
|
||||
final svc = UpdateService();
|
||||
// Reuse service fetch + compare to decide content
|
||||
showLoadingModal(context);
|
||||
final release = await svc.fetchLatestRelease();
|
||||
if (!context.mounted) return;
|
||||
hideLoadingModal(context);
|
||||
if (release != null) {
|
||||
await svc.showUpdateSheet(context, release);
|
||||
} else {
|
||||
showInfoAlert(
|
||||
'Currently cannot get update from the GitHub.',
|
||||
'Unable to check for updates',
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.privacy_tip,
|
||||
title: 'aboutScreenPrivacyPolicyTitle'.tr(),
|
||||
onTap:
|
||||
() => _launchURL(
|
||||
'https://solsynth.dev/terms/privacy-policy',
|
||||
),
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.description,
|
||||
title: 'aboutScreenTermsOfServiceTitle'.tr(),
|
||||
onTap:
|
||||
() => _launchURL(
|
||||
'https://solsynth.dev/terms/user-agreement',
|
||||
),
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.code,
|
||||
title: 'aboutScreenOpenSourceLicensesTitle'.tr(),
|
||||
onTap: () {
|
||||
showLicensePage(
|
||||
context: context,
|
||||
applicationName: _packageInfo.appName,
|
||||
applicationVersion:
|
||||
'Version ${_packageInfo.version}',
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Developer Info
|
||||
_buildSection(
|
||||
context,
|
||||
title: 'aboutScreenDeveloperSectionTitle'.tr(),
|
||||
children: [
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.email,
|
||||
title: 'aboutScreenContactUsTitle'.tr(),
|
||||
subtitle: 'lily@solsynth.dev',
|
||||
onTap:
|
||||
() => _launchURL('mailto:lily@solsynth.dev'),
|
||||
),
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.copyright,
|
||||
title: 'aboutScreenLicenseTitle'.tr(),
|
||||
subtitle: 'aboutScreenLicenseContent'.tr(
|
||||
args: [DateTime.now().year.toString()],
|
||||
),
|
||||
onTap:
|
||||
() => _launchURL(
|
||||
'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
|
||||
),
|
||||
),
|
||||
if (kIsWeb || !(Platform.isMacOS || Platform.isIOS))
|
||||
_buildListTile(
|
||||
context,
|
||||
icon: Symbols.favorite,
|
||||
title: 'donate'.tr(),
|
||||
subtitle: 'donateDescription'.tr(),
|
||||
onTap: () {
|
||||
launchUrlString(
|
||||
'https://afdian.com/@littlesheep',
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Copyright
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'aboutScreenCopyright'.tr(
|
||||
args: [DateTime.now().year.toString()],
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(1),
|
||||
Text(
|
||||
'aboutScreenMadeWith'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
).fontSize(10).opacity(0.8),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Gap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/message.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/screens/notification.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
@@ -15,6 +11,7 @@ import 'package:island/widgets/account/status.dart';
|
||||
import 'package:island/widgets/account/leveling_progress.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/debug_sheet.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
@@ -189,7 +186,6 @@ class AccountScreen extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 8),
|
||||
const Gap(8),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.notifications),
|
||||
@@ -228,6 +224,16 @@ class AccountScreen extends HookConsumerWidget {
|
||||
context.pushNamed('relationships');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.emoji_emotions),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('stickers').tr(),
|
||||
onTap: () {
|
||||
context.pushNamed('stickerMarketplace');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
title: Text('abuseReports').tr(),
|
||||
@@ -267,30 +273,6 @@ class AccountScreen extends HookConsumerWidget {
|
||||
context.pushNamed('accountSettings');
|
||||
},
|
||||
),
|
||||
if (kDebugMode) const Divider(height: 1).padding(vertical: 8),
|
||||
if (kDebugMode)
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.copy_all),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('Copy access token'),
|
||||
onTap: () async {
|
||||
final tk = ref.watch(tokenProvider);
|
||||
Clipboard.setData(ClipboardData(text: tk!.token));
|
||||
},
|
||||
),
|
||||
if (kDebugMode)
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.delete),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('Reset database'),
|
||||
onTap: () async {
|
||||
resetDatabase(ref);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1).padding(vertical: 8),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
@@ -302,6 +284,19 @@ class AccountScreen extends HookConsumerWidget {
|
||||
context.pushNamed('about');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.bug_report),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('debugOptions').tr(),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => DebugSheet(),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.logout),
|
||||
|
@@ -3,9 +3,11 @@ import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
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:image_picker/image_picker.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/user.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
@@ -94,6 +96,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
||||
final usernameController = useTextEditingController(text: user.value!.name);
|
||||
final nicknameController = useTextEditingController(text: user.value!.nick);
|
||||
final language = useState(user.value!.language);
|
||||
final links = useState<List<ProfileLink>>(user.value!.profile.links);
|
||||
|
||||
void updateBasicInfo() async {
|
||||
if (!formKeyBasicInfo.currentState!.validate()) return;
|
||||
@@ -165,6 +168,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
||||
'location': locationController.text,
|
||||
'time_zone': timeZoneController.text,
|
||||
'birthday': birthday.value?.toUtc().toIso8601String(),
|
||||
'links': links.value,
|
||||
},
|
||||
);
|
||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||
@@ -558,6 +562,73 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Text('links').tr().bold().fontSize(18).padding(top: 16),
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (var i = 0; i < links.value.length; i++)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: links.value[i].name,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'linkKey'.tr(),
|
||||
isDense: true,
|
||||
),
|
||||
onChanged: (value) {
|
||||
links.value[i] = links.value[i].copyWith(
|
||||
name: value,
|
||||
);
|
||||
},
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: links.value[i].url,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'linkValue'.tr(),
|
||||
isDense: true,
|
||||
),
|
||||
onChanged: (value) {
|
||||
links.value[i] = links.value[i].copyWith(
|
||||
url: value,
|
||||
);
|
||||
},
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.delete),
|
||||
onPressed: () {
|
||||
links.value = List.from(links.value)
|
||||
..removeAt(i);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
links.value = List.from(links.value)
|
||||
..add(ProfileLink(name: '', url: ''));
|
||||
},
|
||||
label: Text('addLink').tr(),
|
||||
icon: const Icon(Symbols.add),
|
||||
).padding(top: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
@@ -13,6 +14,7 @@ import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/services/color.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/services/text.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/services/timezone/native.dart';
|
||||
import 'package:island/widgets/account/account_name.dart';
|
||||
@@ -30,6 +32,7 @@ import 'package:palette_generator/palette_generator.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
part 'profile.g.dart';
|
||||
|
||||
@@ -194,6 +197,15 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
|
||||
List<Widget> buildSubcolumn(SnAccount data) {
|
||||
return [
|
||||
Row(
|
||||
spacing: 6,
|
||||
children: [
|
||||
const Icon(Symbols.join, size: 17, fill: 1),
|
||||
Text(
|
||||
'joinedAt'.tr(args: [data.createdAt.formatCustom('yyyy-MM-dd')]),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (data.profile.birthday != null)
|
||||
Row(
|
||||
spacing: 6,
|
||||
@@ -320,7 +332,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
spacing: 2,
|
||||
children: buildSubcolumn(data),
|
||||
),
|
||||
if (data.profile.timeZone.isNotEmpty)
|
||||
if (data.profile.timeZone.isNotEmpty && !kIsWeb)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -350,6 +362,32 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
);
|
||||
|
||||
Widget accountProfileLinks(SnAccount data) => Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('links').tr().bold().padding(horizontal: 24, top: 12, bottom: 4),
|
||||
for (final link in data.profile.links)
|
||||
ListTile(
|
||||
title: Text(link.name.capitalizeEachWord()),
|
||||
subtitle: Text(link.url),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
onTap: () {
|
||||
if (!link.url.startsWith('http') && !link.url.contains('://')) {
|
||||
launchUrlString('https://${link.url}');
|
||||
} else {
|
||||
launchUrlString(link.url);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget accountAction(SnAccount data) => Card(
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -452,7 +490,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 8),
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
);
|
||||
|
||||
return account.when(
|
||||
@@ -509,9 +547,11 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||
if (data.badges.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: BadgeList(
|
||||
badges: data.badges,
|
||||
).padding(horizontal: 24, bottom: 24),
|
||||
child: Card(
|
||||
child: BadgeList(
|
||||
badges: data.badges,
|
||||
).padding(horizontal: 26, vertical: 20),
|
||||
).padding(left: 2, right: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
@@ -521,9 +561,10 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
level: data.profile.level,
|
||||
experience: data.profile.experience,
|
||||
progress: data.profile.levelingProgress,
|
||||
),
|
||||
).padding(left: 2, right: 4),
|
||||
if (data.profile.verification != null)
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: VerificationStatusCard(
|
||||
mark: data.profile.verification!,
|
||||
),
|
||||
@@ -534,6 +575,10 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileBio(data).padding(top: 4),
|
||||
),
|
||||
if (data.profile.links.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileLinks(data),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileDetail(data),
|
||||
),
|
||||
@@ -604,9 +649,11 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||
if (data.badges.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: BadgeList(
|
||||
badges: data.badges,
|
||||
).padding(horizontal: 24, bottom: 24),
|
||||
child: Card(
|
||||
child: BadgeList(
|
||||
badges: data.badges,
|
||||
).padding(horizontal: 26, vertical: 20),
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
@@ -628,6 +675,12 @@ class AccountProfileScreen extends HookConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileBio(data).padding(horizontal: 4),
|
||||
),
|
||||
if (data.profile.links.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileLinks(
|
||||
data,
|
||||
).padding(horizontal: 4),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: accountProfileDetail(
|
||||
data,
|
||||
|
@@ -35,6 +35,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'chat.dart';
|
||||
import 'package:island/widgets/chat/call_button.dart';
|
||||
import 'package:island/widgets/stickers/picker.dart';
|
||||
|
||||
part 'room.g.dart';
|
||||
|
||||
@@ -338,7 +339,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
await apiClient.post(
|
||||
'/chat/${chatRoom.value!.id}/members/me',
|
||||
'/sphere/chat/${chatRoom.value!.id}/members/me',
|
||||
);
|
||||
ref.invalidate(chatroomIdentityProvider(id));
|
||||
} catch (err) {
|
||||
@@ -928,7 +929,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
if (attachment.isOnCloud) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
await client.delete(
|
||||
'/files/${attachment.data.id}',
|
||||
'/drive/files/${attachment.data.id}',
|
||||
);
|
||||
}
|
||||
final clone = List.of(attachments.value);
|
||||
@@ -1066,15 +1067,19 @@ class _ChatInput extends HookConsumerWidget {
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, idx) {
|
||||
return AttachmentPreview(
|
||||
item: attachments[idx],
|
||||
onRequestUpload: () => onUploadAttachment(idx),
|
||||
onDelete: () => onDeleteAttachment(idx),
|
||||
onUpdate: (value) {
|
||||
attachments[idx] = value;
|
||||
onAttachmentsChanged(attachments);
|
||||
},
|
||||
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||
return SizedBox(
|
||||
height: 280,
|
||||
width: 280,
|
||||
child: AttachmentPreview(
|
||||
item: attachments[idx],
|
||||
onRequestUpload: () => onUploadAttachment(idx),
|
||||
onDelete: () => onDeleteAttachment(idx),
|
||||
onUpdate: (value) {
|
||||
attachments[idx] = value;
|
||||
onAttachmentsChanged(attachments);
|
||||
},
|
||||
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, _) => const Gap(8),
|
||||
@@ -1129,31 +1134,76 @@ class _ChatInput extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Symbols.photo_library),
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(true),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.photo),
|
||||
Text('addPhoto').tr(),
|
||||
],
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'stickers'.tr(),
|
||||
icon: const Icon(Symbols.emoji_symbols),
|
||||
onPressed: () {
|
||||
final size = MediaQuery.of(context).size;
|
||||
showStickerPickerPopover(
|
||||
context,
|
||||
Offset(
|
||||
20,
|
||||
size.height -
|
||||
480 -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(false),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.video_call),
|
||||
Text('addVideo').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
onPick: (placeholder) {
|
||||
// Insert placeholder at current cursor position
|
||||
final text = messageController.text;
|
||||
final selection = messageController.selection;
|
||||
final start =
|
||||
selection.start >= 0
|
||||
? selection.start
|
||||
: text.length;
|
||||
final end =
|
||||
selection.end >= 0
|
||||
? selection.end
|
||||
: text.length;
|
||||
final newText = text.replaceRange(
|
||||
start,
|
||||
end,
|
||||
placeholder,
|
||||
);
|
||||
messageController.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection: TextSelection.collapsed(
|
||||
offset: start + placeholder.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Symbols.photo_library),
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(true),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.photo),
|
||||
Text('addPhoto').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => onPickFile(false),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
const Icon(Symbols.video_call),
|
||||
Text('addVideo').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: RawKeyboardListener(
|
||||
|
@@ -4,6 +4,8 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/poll.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/poll/poll_feedback.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
@@ -69,7 +71,7 @@ class CreatorPollListScreen extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
return AppScaffold(
|
||||
appBar: AppBar(title: const Text('Polls')),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _createPoll(context),
|
||||
@@ -164,10 +166,13 @@ class _CreatorPollItem extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
// Open editor for edit
|
||||
// Navigator push by path to keep consistency with rest of app:
|
||||
// Note: pub name string may be required in route; when absent, route may need query or pick later.
|
||||
// For safety, just do nothing if no publisher in list item.
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => PollFeedbackSheet(pollId: poll.id, poll: poll),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@@ -58,7 +58,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
||||
try {
|
||||
showLoadingModal(context);
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.delete('/stickers/$id/content/${sticker.id}');
|
||||
await apiClient.delete('/sphere/stickers/$id/content/${sticker.id}');
|
||||
ref.invalidate(stickerPackContentProvider(id));
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
@@ -297,7 +297,7 @@ class _StickerPackActionMenu extends HookConsumerWidget {
|
||||
).then((confirm) {
|
||||
if (confirm) {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
client.delete('/stickers/$packId');
|
||||
client.delete('/sphere/stickers/$packId');
|
||||
ref.invalidate(stickerPacksNotifierProvider);
|
||||
if (context.mounted) context.pop(true);
|
||||
}
|
||||
@@ -325,7 +325,7 @@ Future<SnSticker?> stickerPackSticker(
|
||||
if (query == null) return null;
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get(
|
||||
'/stickers/${query.packId}/content/${query.id}',
|
||||
'/sphere/stickers/${query.packId}/content/${query.id}',
|
||||
);
|
||||
if (resp.data == null) return null;
|
||||
return SnSticker.fromJson(resp.data);
|
||||
@@ -379,8 +379,8 @@ class EditStickersScreen extends HookConsumerWidget {
|
||||
try {
|
||||
final resp = await apiClient.request(
|
||||
id == null
|
||||
? '/stickers/$packId/content'
|
||||
: '/stickers/$packId/content/$id',
|
||||
? '/sphere/stickers/$packId/content'
|
||||
: '/sphere/stickers/$packId/content/$id',
|
||||
data: {'slug': slugController.text, 'image_id': imageController.text},
|
||||
options: Options(method: id == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
|
@@ -151,7 +151,7 @@ class _StickerPackContentProviderElement
|
||||
}
|
||||
|
||||
String _$stickerPackStickerHash() =>
|
||||
r'36f524c047e632236d5597aaaa8678ed86599602';
|
||||
r'5c553666b3a63530bdebae4b7cd52f303c5ab3a0';
|
||||
|
||||
/// See also [stickerPackSticker].
|
||||
@ProviderFor(stickerPackSticker)
|
||||
|
@@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget {
|
||||
context
|
||||
.pushNamed(
|
||||
'creatorStickerPackNew',
|
||||
queryParameters: {'name': pubName},
|
||||
pathParameters: {'name': pubName},
|
||||
)
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
@@ -187,10 +187,8 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
||||
'description': descriptionController.text,
|
||||
'prefix': prefixController.text,
|
||||
},
|
||||
options: Options(
|
||||
method: packId == null ? 'POST' : 'PATCH',
|
||||
headers: {'X-Pub': pubName},
|
||||
),
|
||||
queryParameters: {'pub': pubName},
|
||||
options: Options(method: packId == null ? 'POST' : 'PATCH'),
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
context.pop(SnStickerPack.fromJson(resp.data));
|
||||
|
@@ -114,10 +114,11 @@ class WebFeedEditScreen extends HookConsumerWidget {
|
||||
|
||||
return feedAsync.when(
|
||||
loading:
|
||||
() =>
|
||||
const Scaffold(body: Center(child: CircularProgressIndicator())),
|
||||
() => const AppScaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error:
|
||||
(error, stack) => Scaffold(
|
||||
(error, stack) => AppScaffold(
|
||||
appBar: AppBar(title: const Text('Error')),
|
||||
body: Center(child: Text('Error: $error')),
|
||||
),
|
||||
|
@@ -18,7 +18,7 @@ part 'apps.g.dart';
|
||||
@riverpod
|
||||
Future<List<CustomApp>> customApps(Ref ref, String publisherName) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/developers/$publisherName/apps');
|
||||
final resp = await client.get('/develop/developers/$publisherName/apps');
|
||||
return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@ class CustomAppsScreen extends HookConsumerWidget {
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.add),
|
||||
onPressed: () {
|
||||
context.pushNamed('developerAppNew', pathParameters: {'name': publisherName});
|
||||
context.pushNamed(
|
||||
'developerAppNew',
|
||||
pathParameters: {'name': publisherName},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -121,7 +124,13 @@ class CustomAppsScreen extends HookConsumerWidget {
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 'edit') {
|
||||
context.pushNamed('developerAppEdit', pathParameters: {'name': publisherName, 'id': app.id});
|
||||
context.pushNamed(
|
||||
'developerAppEdit',
|
||||
pathParameters: {
|
||||
'name': publisherName,
|
||||
'id': app.id,
|
||||
},
|
||||
);
|
||||
} else if (value == 'delete') {
|
||||
showConfirmAlert(
|
||||
'deleteCustomAppHint'.tr(),
|
||||
@@ -130,7 +139,7 @@ class CustomAppsScreen extends HookConsumerWidget {
|
||||
if (confirm) {
|
||||
final client = ref.read(apiClientProvider);
|
||||
client.delete(
|
||||
'/developers/$publisherName/apps/${app.id}',
|
||||
'/develop/developers/$publisherName/apps/${app.id}',
|
||||
);
|
||||
ref.invalidate(
|
||||
customAppsProvider(publisherName),
|
||||
|
@@ -6,7 +6,7 @@ part of 'apps.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$customAppsHash() => r'1dec11573b9d987c3adbdf4732b3781a6f40172a';
|
||||
String _$customAppsHash() => r'c6ac78060eb51a2b208a749a81ecbe0a9c608ce1';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@@ -24,7 +24,7 @@ part 'edit_app.g.dart';
|
||||
@riverpod
|
||||
Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/developers/$publisherName/apps/$id');
|
||||
final resp = await client.get('/develop/developers/$publisherName/apps/$id');
|
||||
return CustomApp.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@@ -282,9 +282,15 @@ class EditAppScreen extends HookConsumerWidget {
|
||||
: null,
|
||||
};
|
||||
if (isNew) {
|
||||
await client.post('/developers/$publisherName/apps', data: data);
|
||||
await client.post(
|
||||
'/develop/developers/$publisherName/apps',
|
||||
data: data,
|
||||
);
|
||||
} else {
|
||||
await client.patch('/developers/$publisherName/apps/$id', data: data);
|
||||
await client.patch(
|
||||
'/develop/developers/$publisherName/apps/$id',
|
||||
data: data,
|
||||
);
|
||||
}
|
||||
ref.invalidate(customAppsProvider(publisherName));
|
||||
if (context.mounted) {
|
||||
|
@@ -6,7 +6,7 @@ part of 'edit_app.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$customAppHash() => r'aa4d1fb803c47a99cbacf6d91481f4fce3fda457';
|
||||
String _$customAppHash() => r'42ad937b8439c793e3c5c35568bb5fa4da017df3';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@@ -25,17 +25,17 @@ part 'hub.g.dart';
|
||||
Future<DeveloperStats?> developerStats(Ref ref, String? uname) async {
|
||||
if (uname == null) return null;
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/developers/$uname/stats');
|
||||
final resp = await apiClient.get('/develop/developers/$uname/stats');
|
||||
return DeveloperStats.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<List<SnPublisher>> developers(Ref ref) async {
|
||||
Future<List<SnDeveloper>> developers(Ref ref) async {
|
||||
final client = ref.watch(apiClientProvider);
|
||||
final resp = await client.get('/sphere/developers');
|
||||
final resp = await client.get('/develop/developers');
|
||||
return resp.data
|
||||
.map((e) => SnPublisher.fromJson(e))
|
||||
.cast<SnPublisher>()
|
||||
.map((e) => SnDeveloper.fromJson(e))
|
||||
.cast<SnDeveloper>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -74,25 +74,25 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
final developers = ref.watch(developersProvider);
|
||||
final currentDeveloper = useState<SnPublisher?>(
|
||||
final currentDeveloper = useState<SnDeveloper?>(
|
||||
developers.value?.firstOrNull,
|
||||
);
|
||||
|
||||
final List<DropdownMenuItem<SnPublisher>> developersMenu = developers.when(
|
||||
final List<DropdownMenuItem<SnDeveloper>> developersMenu = developers.when(
|
||||
data:
|
||||
(data) =>
|
||||
data
|
||||
.map(
|
||||
(item) => DropdownMenuItem<SnPublisher>(
|
||||
(item) => DropdownMenuItem<SnDeveloper>(
|
||||
value: item,
|
||||
child: ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: ProfilePictureWidget(
|
||||
radius: 16,
|
||||
fileId: item.picture?.id,
|
||||
fileId: item.publisher?.picture?.id,
|
||||
),
|
||||
title: Text(item.nick),
|
||||
subtitle: Text('@${item.name}'),
|
||||
title: Text(item.publisher!.nick),
|
||||
subtitle: Text('@${item.publisher!.name}'),
|
||||
trailing:
|
||||
currentDeveloper.value?.id == item.id
|
||||
? const Icon(Icons.check)
|
||||
@@ -107,7 +107,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
final developerStats = ref.watch(
|
||||
developerStatsProvider(currentDeveloper.value?.name),
|
||||
developerStatsProvider(currentDeveloper.value?.publisher?.name),
|
||||
);
|
||||
|
||||
return AppScaffold(
|
||||
@@ -117,7 +117,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
title: Text('developerHub').tr(),
|
||||
actions: [
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<SnPublisher>(
|
||||
child: DropdownButton2<SnDeveloper>(
|
||||
alignment: Alignment.centerRight,
|
||||
value: currentDeveloper.value,
|
||||
hint: CircleAvatar(
|
||||
@@ -139,7 +139,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
...developersMenu.map(
|
||||
(e) => ProfilePictureWidget(
|
||||
radius: 16,
|
||||
fileId: e.value?.picture?.id,
|
||||
fileId: e.value?.publisher?.picture?.id,
|
||||
).center().padding(right: 8),
|
||||
),
|
||||
];
|
||||
@@ -193,10 +193,12 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
...(developers.value?.map(
|
||||
(developer) => ListTile(
|
||||
leading: ProfilePictureWidget(
|
||||
file: developer.picture,
|
||||
file: developer.publisher?.picture,
|
||||
),
|
||||
title: Text(developer.publisher!.nick),
|
||||
subtitle: Text(
|
||||
'@${developer.publisher!.name}',
|
||||
),
|
||||
title: Text(developer.nick),
|
||||
subtitle: Text('@${developer.name}'),
|
||||
onTap: () {
|
||||
currentDeveloper.value = developer;
|
||||
},
|
||||
@@ -243,7 +245,8 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
context.pushNamed(
|
||||
'developerApps',
|
||||
pathParameters: {
|
||||
'name': currentDeveloper.value!.name,
|
||||
'name':
|
||||
currentDeveloper.value!.publisher!.name,
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -257,7 +260,9 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
||||
error: err,
|
||||
onRetry: () {
|
||||
ref.invalidate(
|
||||
developerStatsProvider(currentDeveloper.value?.name),
|
||||
developerStatsProvider(
|
||||
currentDeveloper.value?.publisher!.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -336,7 +341,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
|
||||
Future<void> enroll(SnPublisher publisher) async {
|
||||
try {
|
||||
final client = ref.read(apiClientProvider);
|
||||
await client.post('/sphere/developers/${publisher.name}/enroll');
|
||||
await client.post('/develop/developers/${publisher.name}/enroll');
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
@@ -354,7 +359,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
|
||||
? Center(
|
||||
child:
|
||||
Text(
|
||||
'noPublishersToEnroll',
|
||||
'noDevelopersToEnroll',
|
||||
textAlign: TextAlign.center,
|
||||
).tr(),
|
||||
)
|
||||
|
@@ -6,7 +6,7 @@ part of 'hub.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$developerStatsHash() => r'baa708f3586e8987e221cc8ab825d759658c0f55';
|
||||
String _$developerStatsHash() => r'45546f29ec7cd1a9c3a4e0f4e39275e78bf34755';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -149,12 +149,12 @@ class _DeveloperStatsProviderElement
|
||||
String? get uname => (origin as DeveloperStatsProvider).uname;
|
||||
}
|
||||
|
||||
String _$developersHash() => r'f11335fdf553c661110281edeec70ef89c64727d';
|
||||
String _$developersHash() => r'252341098617ac398ce133994453f318dd3edbd2';
|
||||
|
||||
/// See also [developers].
|
||||
@ProviderFor(developers)
|
||||
final developersProvider =
|
||||
AutoDisposeFutureProvider<List<SnPublisher>>.internal(
|
||||
AutoDisposeFutureProvider<List<SnDeveloper>>.internal(
|
||||
developers,
|
||||
name: r'developersProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@@ -167,6 +167,6 @@ final developersProvider =
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnPublisher>>;
|
||||
typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnDeveloper>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
@@ -12,11 +12,11 @@ import 'package:island/models/webfeed.dart';
|
||||
import 'package:island/pods/event_calendar.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/widgets/account/event_calendar.dart';
|
||||
import 'package:island/widgets/account/fortune_graph.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/widgets/check_in.dart';
|
||||
import 'package:island/widgets/post/post_featured.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:island/screens/tabs.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@@ -70,15 +70,6 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
final events = ref.watch(eventCalendarProvider(query.value));
|
||||
|
||||
final selectedDay = useState(now);
|
||||
|
||||
void onMonthChanged(int year, int month) {
|
||||
query.value = EventCalendarQuery(
|
||||
uname: query.value.uname,
|
||||
year: year,
|
||||
month: month,
|
||||
);
|
||||
}
|
||||
|
||||
// Function to handle day selection for synchronizing between widgets
|
||||
void onDaySelected(DateTime day) {
|
||||
selectedDay.value = day;
|
||||
@@ -218,21 +209,16 @@ class ExploreScreen extends HookConsumerWidget {
|
||||
right: 12,
|
||||
top: 16,
|
||||
),
|
||||
onChecked: () {
|
||||
ref.invalidate(
|
||||
eventCalendarProvider(query.value),
|
||||
);
|
||||
},
|
||||
),
|
||||
Card(
|
||||
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
// Use the reusable EventCalendarWidget
|
||||
EventCalendarWidget(
|
||||
events: events,
|
||||
initialDate: now,
|
||||
showEventDetails: true,
|
||||
onMonthChanged: onMonthChanged,
|
||||
onDaySelected: onDaySelected,
|
||||
),
|
||||
],
|
||||
),
|
||||
PostFeaturedList().padding(
|
||||
left: 8,
|
||||
right: 12,
|
||||
top: 8,
|
||||
),
|
||||
FortuneGraphWidget(
|
||||
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
||||
@@ -403,6 +389,10 @@ class _ActivityListView extends HookConsumerWidget {
|
||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: 4),
|
||||
),
|
||||
),
|
||||
if (!contentOnly)
|
||||
SliverToBoxAdapter(
|
||||
child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4),
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: widgetCount,
|
||||
itemBuilder: (context, index) {
|
||||
|
@@ -9,6 +9,7 @@ import 'package:gap/gap.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/models/poll.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class PollEditorState {
|
||||
@@ -413,7 +414,7 @@ class PollEditorScreen extends ConsumerWidget {
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(model.id == null ? 'Create Poll' : 'Edit Poll'),
|
||||
actions: [
|
||||
@@ -428,175 +429,175 @@ class PollEditorScreen extends ConsumerWidget {
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Form(
|
||||
key: ValueKey(model.id),
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
TextFormField(
|
||||
initialValue: model.title ?? '',
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Title',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
maxLength: 256,
|
||||
onChanged: notifier.setTitle,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
validator: (v) {
|
||||
if (v == null || v.trim().isEmpty) {
|
||||
return 'Title is required';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
TextFormField(
|
||||
initialValue: model.description ?? '',
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Description',
|
||||
alignLabelWithHint: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
),
|
||||
maxLines: 3,
|
||||
maxLength: 4096,
|
||||
onChanged: notifier.setDescription,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(12),
|
||||
_EndDatePicker(
|
||||
value: model.endedAt,
|
||||
onChanged: notifier.setEndedAt,
|
||||
),
|
||||
const Gap(24),
|
||||
Row(
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Form(
|
||||
key: ValueKey(model.id),
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Text(
|
||||
'Questions',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const Spacer(),
|
||||
MenuAnchor(
|
||||
builder: (context, controller, child) {
|
||||
return FilledButton.icon(
|
||||
onPressed: () {
|
||||
controller.isOpen
|
||||
? controller.close()
|
||||
: controller.open();
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Add question'),
|
||||
);
|
||||
TextFormField(
|
||||
initialValue: model.title ?? '',
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Title',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
maxLength: 256,
|
||||
onChanged: notifier.setTitle,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
validator: (v) {
|
||||
if (v == null || v.trim().isEmpty) {
|
||||
return 'Title is required';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
menuChildren:
|
||||
SnPollQuestionType.values
|
||||
.map(
|
||||
(t) => MenuItemButton(
|
||||
leadingIcon: Icon(_iconForType(t)),
|
||||
onPressed: () => notifier.addQuestion(t),
|
||||
child: Text(_labelForType(t)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
const Gap(12),
|
||||
TextFormField(
|
||||
initialValue: model.description ?? '',
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Description',
|
||||
alignLabelWithHint: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
),
|
||||
maxLines: 3,
|
||||
maxLength: 4096,
|
||||
onChanged: notifier.setDescription,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
const Gap(12),
|
||||
_EndDatePicker(
|
||||
value: model.endedAt,
|
||||
onChanged: notifier.setEndedAt,
|
||||
),
|
||||
const Gap(24),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Questions',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const Spacer(),
|
||||
MenuAnchor(
|
||||
builder: (context, controller, child) {
|
||||
return FilledButton.icon(
|
||||
onPressed: () {
|
||||
controller.isOpen
|
||||
? controller.close()
|
||||
: controller.open();
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Add question'),
|
||||
);
|
||||
},
|
||||
menuChildren:
|
||||
SnPollQuestionType.values
|
||||
.map(
|
||||
(t) => MenuItemButton(
|
||||
leadingIcon: Icon(_iconForType(t)),
|
||||
onPressed: () => notifier.addQuestion(t),
|
||||
child: Text(_labelForType(t)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
if (model.questions.isEmpty)
|
||||
_EmptyState(
|
||||
title: 'No questions yet',
|
||||
subtitle:
|
||||
'Use "Add question" to start building your poll.',
|
||||
)
|
||||
else
|
||||
ReorderableListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: model.questions.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
// Convert to stepwise moves using provided functions
|
||||
if (newIndex > oldIndex) newIndex -= 1;
|
||||
final steps = newIndex - oldIndex;
|
||||
if (steps == 0) return;
|
||||
if (steps > 0) {
|
||||
for (int i = 0; i < steps; i++) {
|
||||
notifier.moveQuestionDown(oldIndex + i);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i > steps; i--) {
|
||||
notifier.moveQuestionUp(oldIndex + i);
|
||||
}
|
||||
}
|
||||
},
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (context, index) {
|
||||
final q = model.questions[index];
|
||||
return Card(
|
||||
key: ValueKey('q_$index'),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
_QuestionHeader(
|
||||
index: index,
|
||||
question: q,
|
||||
onMoveUp:
|
||||
index > 0
|
||||
? () => notifier.moveQuestionUp(index)
|
||||
: null,
|
||||
onMoveDown:
|
||||
index < model.questions.length - 1
|
||||
? () => notifier.moveQuestionDown(index)
|
||||
: null,
|
||||
onDelete: () => notifier.removeQuestion(index),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: _QuestionEditor(
|
||||
index: index,
|
||||
question: q,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(96),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
if (model.questions.isEmpty)
|
||||
_EmptyState(
|
||||
title: 'No questions yet',
|
||||
subtitle: 'Use "Add question" to start building your poll.',
|
||||
)
|
||||
else
|
||||
ReorderableListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: model.questions.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
// Convert to stepwise moves using provided functions
|
||||
if (newIndex > oldIndex) newIndex -= 1;
|
||||
final steps = newIndex - oldIndex;
|
||||
if (steps == 0) return;
|
||||
if (steps > 0) {
|
||||
for (int i = 0; i < steps; i++) {
|
||||
notifier.moveQuestionDown(oldIndex + i);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i > steps; i--) {
|
||||
notifier.moveQuestionUp(oldIndex + i);
|
||||
}
|
||||
}
|
||||
},
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (context, index) {
|
||||
final q = model.questions[index];
|
||||
return Card(
|
||||
key: ValueKey('q_$index'),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
_QuestionHeader(
|
||||
index: index,
|
||||
question: q,
|
||||
onMoveUp:
|
||||
index > 0
|
||||
? () => notifier.moveQuestionUp(index)
|
||||
: null,
|
||||
onMoveDown:
|
||||
index < model.questions.length - 1
|
||||
? () => notifier.moveQuestionDown(index)
|
||||
: null,
|
||||
onDelete: () => notifier.removeQuestion(index),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: _QuestionEditor(index: index, question: q),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(96),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
label: const Text('Cancel'),
|
||||
),
|
||||
const Spacer(),
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
_submitPoll(context, ref);
|
||||
},
|
||||
icon: const Icon(Icons.cloud_upload_outlined),
|
||||
label: Text(model.id == null ? 'Create' : 'Update'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
16,
|
||||
8,
|
||||
16,
|
||||
16 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
icon: const Icon(Icons.close),
|
||||
label: const Text('Cancel'),
|
||||
),
|
||||
const Spacer(),
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
_submitPoll(context, ref);
|
||||
},
|
||||
icon: const Icon(Icons.cloud_upload_outlined),
|
||||
label: Text(model.id == null ? 'Create' : 'Update'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@@ -205,17 +205,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => ComposeSettingsSheet(
|
||||
titleController: state.titleController,
|
||||
descriptionController: state.descriptionController,
|
||||
visibility: state.visibility,
|
||||
tagsController: state.tagsController,
|
||||
categoriesController: state.categoriesController,
|
||||
onVisibilityChanged: () {
|
||||
// Trigger rebuild if needed
|
||||
},
|
||||
),
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -369,31 +359,73 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
|
||||
// Post content form
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Content field with borderless design
|
||||
RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey:
|
||||
(event) => ComposeLogic.handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
ref,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
repliedPost: repliedPost,
|
||||
forwardedPost: forwardedPost,
|
||||
child: KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent:
|
||||
(event) => ComposeLogic.handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
ref,
|
||||
context,
|
||||
originalPost: originalPost,
|
||||
repliedPost: repliedPost,
|
||||
forwardedPost: forwardedPost,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: state.titleController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postTitle'.tr(),
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: TextField(
|
||||
),
|
||||
style: theme.textTheme.titleMedium,
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
TextField(
|
||||
controller: state.descriptionController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postDescription'.tr(),
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.fromLTRB(
|
||||
8,
|
||||
4,
|
||||
8,
|
||||
12,
|
||||
),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
// Content field with borderless design
|
||||
TextField(
|
||||
controller: state.contentController,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'postContent'.tr(),
|
||||
contentPadding: const EdgeInsets.all(8),
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
maxLines: null,
|
||||
onTapOutside:
|
||||
@@ -401,23 +433,23 @@ class PostComposeScreen extends HookConsumerWidget {
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
),
|
||||
),
|
||||
|
||||
const Gap(8),
|
||||
const Gap(8),
|
||||
|
||||
// Attachments preview
|
||||
if (state.attachments.value.isNotEmpty)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWide = isWideScreen(context);
|
||||
return isWide
|
||||
? buildWideAttachmentGrid()
|
||||
: buildNarrowAttachmentList();
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
// Attachments preview
|
||||
if (state.attachments.value.isNotEmpty)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWide = isWideScreen(context);
|
||||
return isWide
|
||||
? buildWideAttachmentGrid()
|
||||
: buildNarrowAttachmentList();
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@@ -138,17 +138,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => ComposeSettingsSheet(
|
||||
titleController: state.titleController,
|
||||
descriptionController: state.descriptionController,
|
||||
visibility: state.visibility,
|
||||
tagsController: state.tagsController,
|
||||
categoriesController: state.categoriesController,
|
||||
onVisibilityChanged: () {
|
||||
// Trigger rebuild if needed
|
||||
},
|
||||
),
|
||||
builder: (context) => ComposeSettingsSheet(state: state),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -242,10 +232,39 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: state.titleController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postTitle'.tr(),
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
style: theme.textTheme.titleMedium,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
TextField(
|
||||
controller: state.descriptionController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'postDescription'.tr(),
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
minLines: 1,
|
||||
maxLines: 3,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
Expanded(
|
||||
child: RawKeyboardListener(
|
||||
child: KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey:
|
||||
onKeyEvent:
|
||||
(event) => _handleKeyPress(
|
||||
event,
|
||||
state,
|
||||
@@ -454,7 +473,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
flex: showPreview.value ? 1 : 2,
|
||||
child: buildEditorPane(),
|
||||
),
|
||||
const VerticalDivider(),
|
||||
if (showPreview.value) const VerticalDivider(),
|
||||
if (showPreview.value)
|
||||
Expanded(child: buildPreviewPane()),
|
||||
],
|
||||
@@ -475,7 +494,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
|
||||
// Helper method to handle keyboard shortcuts
|
||||
void _handleKeyPress(
|
||||
RawKeyEvent event,
|
||||
KeyEvent event,
|
||||
ComposeState state,
|
||||
WidgetRef ref,
|
||||
BuildContext context, {
|
||||
@@ -485,7 +504,9 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
||||
|
||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
||||
final isModifierPressed =
|
||||
HardwareKeyboard.instance.isMetaPressed ||
|
||||
HardwareKeyboard.instance.isControlPressed;
|
||||
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
||||
|
||||
if (isPaste && isModifierPressed) {
|
||||
|
107
lib/screens/posts/post_category_detail.dart
Normal file
107
lib/screens/posts/post_category_detail.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post_category.dart';
|
||||
import 'package:island/models/post_tag.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/post/post_list.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'post_category_detail.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<SnPostCategory> postCategory(Ref ref, String slug) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/posts/categories/$slug');
|
||||
return SnPostCategory.fromJson(resp.data);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<SnPostTag> postTag(Ref ref, String slug) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/posts/tags/$slug');
|
||||
return SnPostTag.fromJson(resp.data);
|
||||
}
|
||||
|
||||
class PostCategoryDetailScreen extends HookConsumerWidget {
|
||||
final String slug;
|
||||
final bool isCategory;
|
||||
const PostCategoryDetailScreen({
|
||||
super.key,
|
||||
required this.slug,
|
||||
required this.isCategory,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final postCategory =
|
||||
isCategory ? ref.watch(postCategoryProvider(slug)) : null;
|
||||
final postTag = isCategory ? null : ref.watch(postTagProvider(slug));
|
||||
|
||||
final postFilterTitle =
|
||||
isCategory
|
||||
? postCategory?.value?.categoryDisplayTitle ?? 'loading'
|
||||
: postTag?.value?.name ?? postTag?.value?.slug ?? 'loading';
|
||||
|
||||
return AppScaffold(
|
||||
isNoBackground: false,
|
||||
appBar: AppBar(title: Text(postFilterTitle).tr()),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isCategory)
|
||||
postCategory!.when(
|
||||
data:
|
||||
(category) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(category.categoryDisplayTitle).bold().fontSize(15),
|
||||
Text('A category'),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postCategoryProvider(slug)),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
)
|
||||
else
|
||||
postTag!.when(
|
||||
data:
|
||||
(tag) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(tag.name ?? '#${tag.slug}').bold().fontSize(15),
|
||||
Text('A tag'),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 16),
|
||||
error:
|
||||
(error, _) => ResponseErrorWidget(
|
||||
error: error,
|
||||
onRetry: () => ref.invalidate(postTagProvider(slug)),
|
||||
),
|
||||
loading: () => ResponseLoadingWidget(),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
const SliverGap(4),
|
||||
SliverPostList(
|
||||
categories: isCategory ? [slug] : null,
|
||||
tags: isCategory ? null : [slug],
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
270
lib/screens/posts/post_category_detail.g.dart
Normal file
270
lib/screens/posts/post_category_detail.g.dart
Normal file
@@ -0,0 +1,270 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'post_category_detail.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$postCategoryHash() => r'0df2de729ba96819ee37377314615abef0c99547';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [postCategory].
|
||||
@ProviderFor(postCategory)
|
||||
const postCategoryProvider = PostCategoryFamily();
|
||||
|
||||
/// See also [postCategory].
|
||||
class PostCategoryFamily extends Family<AsyncValue<SnPostCategory>> {
|
||||
/// See also [postCategory].
|
||||
const PostCategoryFamily();
|
||||
|
||||
/// See also [postCategory].
|
||||
PostCategoryProvider call(String slug) {
|
||||
return PostCategoryProvider(slug);
|
||||
}
|
||||
|
||||
@override
|
||||
PostCategoryProvider getProviderOverride(
|
||||
covariant PostCategoryProvider provider,
|
||||
) {
|
||||
return call(provider.slug);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'postCategoryProvider';
|
||||
}
|
||||
|
||||
/// See also [postCategory].
|
||||
class PostCategoryProvider extends AutoDisposeFutureProvider<SnPostCategory> {
|
||||
/// See also [postCategory].
|
||||
PostCategoryProvider(String slug)
|
||||
: this._internal(
|
||||
(ref) => postCategory(ref as PostCategoryRef, slug),
|
||||
from: postCategoryProvider,
|
||||
name: r'postCategoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postCategoryHash,
|
||||
dependencies: PostCategoryFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
PostCategoryFamily._allTransitiveDependencies,
|
||||
slug: slug,
|
||||
);
|
||||
|
||||
PostCategoryProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.slug,
|
||||
}) : super.internal();
|
||||
|
||||
final String slug;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnPostCategory> Function(PostCategoryRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PostCategoryProvider._internal(
|
||||
(ref) => create(ref as PostCategoryRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
slug: slug,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnPostCategory> createElement() {
|
||||
return _PostCategoryProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PostCategoryProvider && other.slug == slug;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, slug.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PostCategoryRef on AutoDisposeFutureProviderRef<SnPostCategory> {
|
||||
/// The parameter `slug` of this provider.
|
||||
String get slug;
|
||||
}
|
||||
|
||||
class _PostCategoryProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnPostCategory>
|
||||
with PostCategoryRef {
|
||||
_PostCategoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get slug => (origin as PostCategoryProvider).slug;
|
||||
}
|
||||
|
||||
String _$postTagHash() => r'e050fdf9af81a843a9abd9cf979dd2672e0a2b93';
|
||||
|
||||
/// See also [postTag].
|
||||
@ProviderFor(postTag)
|
||||
const postTagProvider = PostTagFamily();
|
||||
|
||||
/// See also [postTag].
|
||||
class PostTagFamily extends Family<AsyncValue<SnPostTag>> {
|
||||
/// See also [postTag].
|
||||
const PostTagFamily();
|
||||
|
||||
/// See also [postTag].
|
||||
PostTagProvider call(String slug) {
|
||||
return PostTagProvider(slug);
|
||||
}
|
||||
|
||||
@override
|
||||
PostTagProvider getProviderOverride(covariant PostTagProvider provider) {
|
||||
return call(provider.slug);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'postTagProvider';
|
||||
}
|
||||
|
||||
/// See also [postTag].
|
||||
class PostTagProvider extends AutoDisposeFutureProvider<SnPostTag> {
|
||||
/// See also [postTag].
|
||||
PostTagProvider(String slug)
|
||||
: this._internal(
|
||||
(ref) => postTag(ref as PostTagRef, slug),
|
||||
from: postTagProvider,
|
||||
name: r'postTagProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postTagHash,
|
||||
dependencies: PostTagFamily._dependencies,
|
||||
allTransitiveDependencies: PostTagFamily._allTransitiveDependencies,
|
||||
slug: slug,
|
||||
);
|
||||
|
||||
PostTagProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.slug,
|
||||
}) : super.internal();
|
||||
|
||||
final String slug;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<SnPostTag> Function(PostTagRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PostTagProvider._internal(
|
||||
(ref) => create(ref as PostTagRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
slug: slug,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<SnPostTag> createElement() {
|
||||
return _PostTagProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PostTagProvider && other.slug == slug;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, slug.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PostTagRef on AutoDisposeFutureProviderRef<SnPostTag> {
|
||||
/// The parameter `slug` of this provider.
|
||||
String get slug;
|
||||
}
|
||||
|
||||
class _PostTagProviderElement
|
||||
extends AutoDisposeFutureProviderElement<SnPostTag>
|
||||
with PostTagRef {
|
||||
_PostTagProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get slug => (origin as PostTagProvider).slug;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
@@ -92,6 +92,7 @@ class PostDetailScreen extends HookConsumerWidget {
|
||||
right: 0,
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: postState
|
||||
.when(
|
||||
data:
|
||||
@@ -107,8 +108,8 @@ class PostDetailScreen extends HookConsumerWidget {
|
||||
error: (_, _) => const SizedBox.shrink(),
|
||||
)
|
||||
.padding(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||
top: 16,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 8,
|
||||
top: 8,
|
||||
horizontal: 16,
|
||||
),
|
||||
),
|
||||
|
@@ -87,13 +87,22 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
publisherAppbarForcegroundColorProvider(name),
|
||||
);
|
||||
|
||||
final categoryTabController = useTabController(initialLength: 3);
|
||||
final categoryTab = useState(0);
|
||||
categoryTabController.addListener(() {
|
||||
categoryTab.value = categoryTabController.index;
|
||||
});
|
||||
|
||||
final subscribing = useState(false);
|
||||
|
||||
Future<void> subscribe() async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
subscribing.value = true;
|
||||
try {
|
||||
await apiClient.post("/publishers/$name/subscribe", data: {'tier': 0});
|
||||
await apiClient.post(
|
||||
"/sphere/publishers/$name/subscribe",
|
||||
data: {'tier': 0},
|
||||
);
|
||||
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
||||
HapticFeedback.heavyImpact();
|
||||
} catch (err) {
|
||||
@@ -107,7 +116,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
subscribing.value = true;
|
||||
try {
|
||||
await apiClient.post("/publishers/$name/unsubscribe");
|
||||
await apiClient.post("/sphere/publishers/$name/unsubscribe");
|
||||
ref.invalidate(publisherSubscriptionStatusProvider(name));
|
||||
HapticFeedback.heavyImpact();
|
||||
} catch (err) {
|
||||
@@ -268,6 +277,16 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
).padding(horizontal: 20, vertical: 16),
|
||||
);
|
||||
|
||||
Widget publisherCategoryTabWidget() => Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: TabBar(
|
||||
controller: categoryTabController,
|
||||
dividerColor: Colors.transparent,
|
||||
splashBorderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
tabs: [Tab(text: 'All'), Tab(text: 'Posts'), Tab(text: 'Articles')],
|
||||
),
|
||||
);
|
||||
|
||||
return publisher.when(
|
||||
data:
|
||||
(data) => AppScaffold(
|
||||
@@ -321,7 +340,18 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverGap(16),
|
||||
SliverPostList(pubName: name),
|
||||
SliverToBoxAdapter(
|
||||
child: publisherCategoryTabWidget(),
|
||||
),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
pubName: name,
|
||||
type: switch (categoryTab.value) {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
_ => null,
|
||||
},
|
||||
),
|
||||
SliverGap(
|
||||
MediaQuery.of(context).padding.bottom + 16,
|
||||
),
|
||||
@@ -334,9 +364,9 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
alignment: Alignment.topLeft,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
publisherBasisWidget(data),
|
||||
publisherBasisWidget(data).padding(bottom: 8),
|
||||
publisherBadgesWidget(data),
|
||||
publisherVerificationWidget(data),
|
||||
publisherBioWidget(data),
|
||||
@@ -398,7 +428,16 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
||||
child: publisherVerificationWidget(data),
|
||||
),
|
||||
SliverToBoxAdapter(child: publisherBioWidget(data)),
|
||||
SliverPostList(pubName: name),
|
||||
SliverToBoxAdapter(child: publisherCategoryTabWidget()),
|
||||
SliverPostList(
|
||||
key: ValueKey(categoryTab.value),
|
||||
pubName: name,
|
||||
type: switch (categoryTab.value) {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
_ => null,
|
||||
},
|
||||
),
|
||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||
],
|
||||
),
|
||||
|
103
lib/screens/stickers/marketplace.dart
Normal file
103
lib/screens/stickers/marketplace.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/sticker.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
|
||||
part 'marketplace.g.dart';
|
||||
|
||||
@riverpod
|
||||
class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier
|
||||
with CursorPagingNotifierMixin<SnStickerPack> {
|
||||
@override
|
||||
Future<CursorPagingData<SnStickerPack>> build() {
|
||||
return fetch(cursor: null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<SnStickerPack>> fetch({
|
||||
required String? cursor,
|
||||
}) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||
|
||||
final response = await client.get(
|
||||
'/sphere/stickers',
|
||||
queryParameters: {'offset': offset, 'take': 20},
|
||||
);
|
||||
|
||||
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||
final List<dynamic> data = response.data;
|
||||
final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList();
|
||||
|
||||
final hasMore = offset + stickers.length < total;
|
||||
final nextCursor = hasMore ? (offset + stickers.length).toString() : null;
|
||||
|
||||
return CursorPagingData(
|
||||
items: stickers,
|
||||
hasMore: hasMore,
|
||||
nextCursor: nextCursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// User-facing marketplace screen for browsing sticker packs.
|
||||
/// This version does NOT rely on publisher name (no pubName).
|
||||
class MarketplaceStickersScreen extends HookConsumerWidget {
|
||||
const MarketplaceStickersScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('stickers').tr(),
|
||||
actions: const [Gap(8)],
|
||||
),
|
||||
body: const SliverMarketplaceStickerPacksList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SliverMarketplaceStickerPacksList extends HookConsumerWidget {
|
||||
const SliverMarketplaceStickerPacksList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return PagingHelperView(
|
||||
provider: marketplaceStickerPacksNotifierProvider,
|
||||
futureRefreshable: marketplaceStickerPacksNotifierProvider.future,
|
||||
notifierRefreshable: marketplaceStickerPacksNotifierProvider.notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) => ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: widgetCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
return endItemView;
|
||||
}
|
||||
|
||||
final pack = data.items[index];
|
||||
return ListTile(
|
||||
title: Text(pack.name),
|
||||
subtitle: Text(pack.description),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
// Navigate to user-facing sticker pack detail page.
|
||||
// Adjust the route name/parameters if your app uses different ones.
|
||||
context.pushNamed(
|
||||
'stickerPackDetail',
|
||||
pathParameters: {'packId': pack.id},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
32
lib/screens/stickers/marketplace.g.dart
Normal file
32
lib/screens/stickers/marketplace.g.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'marketplace.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$marketplaceStickerPacksNotifierHash() =>
|
||||
r'b62ae8b7f5c4f8bb3be8c17fc005ea26da355187';
|
||||
|
||||
/// See also [MarketplaceStickerPacksNotifier].
|
||||
@ProviderFor(MarketplaceStickerPacksNotifier)
|
||||
final marketplaceStickerPacksNotifierProvider =
|
||||
AutoDisposeAsyncNotifierProvider<
|
||||
MarketplaceStickerPacksNotifier,
|
||||
CursorPagingData<SnStickerPack>
|
||||
>.internal(
|
||||
MarketplaceStickerPacksNotifier.new,
|
||||
name: r'marketplaceStickerPacksNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$marketplaceStickerPacksNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$MarketplaceStickerPacksNotifier =
|
||||
AutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
230
lib/screens/stickers/pack_detail.dart
Normal file
230
lib/screens/stickers/pack_detail.dart
Normal file
@@ -0,0 +1,230 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/sticker.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'pack_detail.g.dart'; // generated by riverpod_annotation build_runner
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
@riverpod
|
||||
Future<List<SnSticker>> marketplaceStickerPackContent(
|
||||
Ref ref, {
|
||||
required String packId,
|
||||
}) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/stickers/$packId/content');
|
||||
return (resp.data as List).map((e) => SnSticker.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<bool> marketplaceStickerPackOwnership(
|
||||
Ref ref, {
|
||||
required String packId,
|
||||
}) async {
|
||||
final api = ref.watch(apiClientProvider);
|
||||
try {
|
||||
await api.get('/sphere/stickers/$packId/own');
|
||||
// If not 404, consider owned
|
||||
return true;
|
||||
} on Object catch (e) {
|
||||
// Dio error handling agnostic: treat 404 as not-owned, rethrow others
|
||||
final msg = e.toString();
|
||||
if (msg.contains('404')) return false;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
class MarketplaceStickerPackDetailScreen extends HookConsumerWidget {
|
||||
final String id;
|
||||
const MarketplaceStickerPackDetailScreen({super.key, required this.id});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Pack metadata provider exists globally in creators file; reuse it.
|
||||
final pack = ref.watch(stickerPackProvider(id));
|
||||
final packContent = ref.watch(
|
||||
marketplaceStickerPackContentProvider(packId: id),
|
||||
);
|
||||
final owned = ref.watch(
|
||||
marketplaceStickerPackOwnershipProvider(packId: id),
|
||||
);
|
||||
|
||||
// Add entire pack to user's collection
|
||||
Future<void> addPackToMyCollection() async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.post('/sphere/stickers/$id/own');
|
||||
HapticFeedback.selectionClick();
|
||||
ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
|
||||
if (!context.mounted) return;
|
||||
showSnackBar('stickerPackAdded'.tr());
|
||||
}
|
||||
|
||||
// Remove ownership of the pack
|
||||
Future<void> removePackFromMyCollection() async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
await apiClient.delete('/sphere/stickers/$id/own');
|
||||
HapticFeedback.selectionClick();
|
||||
ref.invalidate(marketplaceStickerPackOwnershipProvider(packId: id));
|
||||
if (!context.mounted) return;
|
||||
showSnackBar('stickerPackRemoved'.tr());
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(title: Text(pack.value?.name ?? 'loading'.tr())),
|
||||
body: pack.when(
|
||||
data: (p) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Pack meta
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(p?.description ?? ''),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
const Icon(Symbols.folder, size: 16),
|
||||
Text(
|
||||
'${packContent.value?.length ?? 0}/24',
|
||||
style: GoogleFonts.robotoMono(),
|
||||
),
|
||||
],
|
||||
).opacity(0.85),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
const Icon(Symbols.sell, size: 16),
|
||||
Text(p?.prefix ?? '', style: GoogleFonts.robotoMono()),
|
||||
],
|
||||
).opacity(0.85),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
const Icon(Symbols.tag, size: 16),
|
||||
SelectableText(
|
||||
p?.id ?? id,
|
||||
style: GoogleFonts.robotoMono(),
|
||||
),
|
||||
],
|
||||
).opacity(0.85),
|
||||
],
|
||||
).padding(horizontal: 24, vertical: 24),
|
||||
const Divider(height: 1),
|
||||
// Stickers grid
|
||||
Expanded(
|
||||
child: packContent.when(
|
||||
data:
|
||||
(stickers) => RefreshIndicator(
|
||||
onRefresh:
|
||||
() => ref.refresh(
|
||||
marketplaceStickerPackContentProvider(
|
||||
packId: id,
|
||||
).future,
|
||||
),
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 20,
|
||||
),
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 96,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
),
|
||||
itemCount: stickers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final sticker = stickers[index];
|
||||
return Tooltip(
|
||||
message: ':${p?.prefix ?? ''}${sticker.slug}:',
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: CloudImageWidget(
|
||||
fileId: sticker.imageId,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
error:
|
||||
(err, _) =>
|
||||
Text(
|
||||
'Error: $err',
|
||||
).textAlignment(TextAlign.center).center(),
|
||||
loading: () => const CircularProgressIndicator().center(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
child: owned.when(
|
||||
data:
|
||||
(isOwned) => FilledButton.icon(
|
||||
onPressed:
|
||||
isOwned
|
||||
? removePackFromMyCollection
|
||||
: addPackToMyCollection,
|
||||
icon: Icon(
|
||||
isOwned ? Symbols.remove_circle : Symbols.add_circle,
|
||||
),
|
||||
label: Text(
|
||||
isOwned ? 'removePack'.tr() : 'addPack'.tr(),
|
||||
),
|
||||
),
|
||||
loading:
|
||||
() => const SizedBox(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
error:
|
||||
(_, _) => OutlinedButton.icon(
|
||||
onPressed: addPackToMyCollection,
|
||||
icon: const Icon(Symbols.add_circle),
|
||||
label: Text('addPack').tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Gap(MediaQuery.of(context).padding.bottom),
|
||||
],
|
||||
);
|
||||
},
|
||||
error:
|
||||
(err, _) =>
|
||||
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
||||
loading: () => const CircularProgressIndicator().center(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
317
lib/screens/stickers/pack_detail.g.dart
Normal file
317
lib/screens/stickers/pack_detail.g.dart
Normal file
@@ -0,0 +1,317 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'pack_detail.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$marketplaceStickerPackContentHash() =>
|
||||
r'886f8305c978dbea6e5d990a7d555048ac704a5d';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
@ProviderFor(marketplaceStickerPackContent)
|
||||
const marketplaceStickerPackContentProvider =
|
||||
MarketplaceStickerPackContentFamily();
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
class MarketplaceStickerPackContentFamily
|
||||
extends Family<AsyncValue<List<SnSticker>>> {
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
const MarketplaceStickerPackContentFamily();
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
MarketplaceStickerPackContentProvider call({required String packId}) {
|
||||
return MarketplaceStickerPackContentProvider(packId: packId);
|
||||
}
|
||||
|
||||
@override
|
||||
MarketplaceStickerPackContentProvider getProviderOverride(
|
||||
covariant MarketplaceStickerPackContentProvider provider,
|
||||
) {
|
||||
return call(packId: provider.packId);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'marketplaceStickerPackContentProvider';
|
||||
}
|
||||
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
class MarketplaceStickerPackContentProvider
|
||||
extends AutoDisposeFutureProvider<List<SnSticker>> {
|
||||
/// Marketplace version of sticker pack detail page (no publisher dependency).
|
||||
/// Shows all stickers in the pack and provides a button to add the sticker.
|
||||
/// API interactions are intentionally left blank per request.
|
||||
///
|
||||
/// Copied from [marketplaceStickerPackContent].
|
||||
MarketplaceStickerPackContentProvider({required String packId})
|
||||
: this._internal(
|
||||
(ref) => marketplaceStickerPackContent(
|
||||
ref as MarketplaceStickerPackContentRef,
|
||||
packId: packId,
|
||||
),
|
||||
from: marketplaceStickerPackContentProvider,
|
||||
name: r'marketplaceStickerPackContentProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$marketplaceStickerPackContentHash,
|
||||
dependencies: MarketplaceStickerPackContentFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
MarketplaceStickerPackContentFamily._allTransitiveDependencies,
|
||||
packId: packId,
|
||||
);
|
||||
|
||||
MarketplaceStickerPackContentProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.packId,
|
||||
}) : super.internal();
|
||||
|
||||
final String packId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<List<SnSticker>> Function(
|
||||
MarketplaceStickerPackContentRef provider,
|
||||
)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: MarketplaceStickerPackContentProvider._internal(
|
||||
(ref) => create(ref as MarketplaceStickerPackContentRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
packId: packId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<List<SnSticker>> createElement() {
|
||||
return _MarketplaceStickerPackContentProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MarketplaceStickerPackContentProvider &&
|
||||
other.packId == packId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, packId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin MarketplaceStickerPackContentRef
|
||||
on AutoDisposeFutureProviderRef<List<SnSticker>> {
|
||||
/// The parameter `packId` of this provider.
|
||||
String get packId;
|
||||
}
|
||||
|
||||
class _MarketplaceStickerPackContentProviderElement
|
||||
extends AutoDisposeFutureProviderElement<List<SnSticker>>
|
||||
with MarketplaceStickerPackContentRef {
|
||||
_MarketplaceStickerPackContentProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get packId => (origin as MarketplaceStickerPackContentProvider).packId;
|
||||
}
|
||||
|
||||
String _$marketplaceStickerPackOwnershipHash() =>
|
||||
r'e5dd301c309fac958729d13d984ce7a77edbe7e6';
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
@ProviderFor(marketplaceStickerPackOwnership)
|
||||
const marketplaceStickerPackOwnershipProvider =
|
||||
MarketplaceStickerPackOwnershipFamily();
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
class MarketplaceStickerPackOwnershipFamily extends Family<AsyncValue<bool>> {
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
const MarketplaceStickerPackOwnershipFamily();
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
MarketplaceStickerPackOwnershipProvider call({required String packId}) {
|
||||
return MarketplaceStickerPackOwnershipProvider(packId: packId);
|
||||
}
|
||||
|
||||
@override
|
||||
MarketplaceStickerPackOwnershipProvider getProviderOverride(
|
||||
covariant MarketplaceStickerPackOwnershipProvider provider,
|
||||
) {
|
||||
return call(packId: provider.packId);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'marketplaceStickerPackOwnershipProvider';
|
||||
}
|
||||
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
class MarketplaceStickerPackOwnershipProvider
|
||||
extends AutoDisposeFutureProvider<bool> {
|
||||
/// See also [marketplaceStickerPackOwnership].
|
||||
MarketplaceStickerPackOwnershipProvider({required String packId})
|
||||
: this._internal(
|
||||
(ref) => marketplaceStickerPackOwnership(
|
||||
ref as MarketplaceStickerPackOwnershipRef,
|
||||
packId: packId,
|
||||
),
|
||||
from: marketplaceStickerPackOwnershipProvider,
|
||||
name: r'marketplaceStickerPackOwnershipProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$marketplaceStickerPackOwnershipHash,
|
||||
dependencies: MarketplaceStickerPackOwnershipFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
MarketplaceStickerPackOwnershipFamily._allTransitiveDependencies,
|
||||
packId: packId,
|
||||
);
|
||||
|
||||
MarketplaceStickerPackOwnershipProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.packId,
|
||||
}) : super.internal();
|
||||
|
||||
final String packId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<bool> Function(MarketplaceStickerPackOwnershipRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: MarketplaceStickerPackOwnershipProvider._internal(
|
||||
(ref) => create(ref as MarketplaceStickerPackOwnershipRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
packId: packId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<bool> createElement() {
|
||||
return _MarketplaceStickerPackOwnershipProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MarketplaceStickerPackOwnershipProvider &&
|
||||
other.packId == packId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, packId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin MarketplaceStickerPackOwnershipRef on AutoDisposeFutureProviderRef<bool> {
|
||||
/// The parameter `packId` of this provider.
|
||||
String get packId;
|
||||
}
|
||||
|
||||
class _MarketplaceStickerPackOwnershipProviderElement
|
||||
extends AutoDisposeFutureProviderElement<bool>
|
||||
with MarketplaceStickerPackOwnershipRef {
|
||||
_MarketplaceStickerPackOwnershipProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get packId =>
|
||||
(origin as MarketplaceStickerPackOwnershipProvider).packId;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
395
lib/services/update_service.dart
Normal file
395
lib/services/update_service.dart
Normal file
@@ -0,0 +1,395 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app_update/azhon_app_update.dart';
|
||||
import 'package:flutter_app_update/update_model.dart';
|
||||
import 'package:island/widgets/content/markdown.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:collection/collection.dart'; // Added for firstWhereOrNull
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
|
||||
/// Data model for a GitHub release we care about
|
||||
class GithubReleaseInfo {
|
||||
final String tagName;
|
||||
final String name;
|
||||
final String body;
|
||||
final String htmlUrl;
|
||||
final DateTime createdAt;
|
||||
final List<GithubReleaseAsset> assets;
|
||||
|
||||
const GithubReleaseInfo({
|
||||
required this.tagName,
|
||||
required this.name,
|
||||
required this.body,
|
||||
required this.htmlUrl,
|
||||
required this.createdAt,
|
||||
this.assets = const [],
|
||||
});
|
||||
}
|
||||
|
||||
/// Data model for a GitHub release asset
|
||||
class GithubReleaseAsset {
|
||||
final String name;
|
||||
final String browserDownloadUrl;
|
||||
|
||||
const GithubReleaseAsset({
|
||||
required this.name,
|
||||
required this.browserDownloadUrl,
|
||||
});
|
||||
|
||||
factory GithubReleaseAsset.fromJson(Map<String, dynamic> json) {
|
||||
return GithubReleaseAsset(
|
||||
name: json['name'] as String,
|
||||
browserDownloadUrl: json['browser_download_url'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses version and build number from "x.y.z+build"
|
||||
class _ParsedVersion implements Comparable<_ParsedVersion> {
|
||||
final int major;
|
||||
final int minor;
|
||||
final int patch;
|
||||
final int build;
|
||||
|
||||
const _ParsedVersion(this.major, this.minor, this.patch, this.build);
|
||||
|
||||
static _ParsedVersion? tryParse(String input) {
|
||||
// Expect format like 0.0.0+00 (build after '+'). Allow missing build as 0.
|
||||
final partsPlus = input.split('+');
|
||||
final core = partsPlus[0].trim();
|
||||
final buildStr = partsPlus.length > 1 ? partsPlus[1].trim() : '0';
|
||||
final coreParts = core.split('.');
|
||||
if (coreParts.length != 3) return null;
|
||||
|
||||
final major = int.tryParse(coreParts[0]) ?? 0;
|
||||
final minor = int.tryParse(coreParts[1]) ?? 0;
|
||||
final patch = int.tryParse(coreParts[2]) ?? 0;
|
||||
final build = int.tryParse(buildStr) ?? 0;
|
||||
|
||||
return _ParsedVersion(major, minor, patch, build);
|
||||
}
|
||||
|
||||
@override
|
||||
int compareTo(_ParsedVersion other) {
|
||||
if (major != other.major) return major.compareTo(other.major);
|
||||
if (minor != other.minor) return minor.compareTo(other.minor);
|
||||
if (patch != other.patch) return patch.compareTo(other.patch);
|
||||
return build.compareTo(other.build);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$major.$minor.$patch+$build';
|
||||
}
|
||||
|
||||
class UpdateService {
|
||||
UpdateService({Dio? dio, this.useProxy = false})
|
||||
: _dio =
|
||||
dio ??
|
||||
Dio(
|
||||
BaseOptions(
|
||||
headers: {
|
||||
// Identify the app to GitHub; avoids some rate-limits and adds clarity
|
||||
'Accept': 'application/vnd.github+json',
|
||||
'User-Agent': 'solian-update-checker',
|
||||
},
|
||||
connectTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 15),
|
||||
),
|
||||
);
|
||||
|
||||
final Dio _dio;
|
||||
final bool useProxy;
|
||||
|
||||
static const _proxyBaseUrl = 'https://ghfast.top/';
|
||||
|
||||
static const _releasesLatestApi =
|
||||
'https://api.github.com/repos/solsynth/solian/releases/latest';
|
||||
|
||||
/// Checks GitHub for the latest release and compares against the current app version.
|
||||
/// If update is available, shows a bottom sheet with changelog and an action to open release page.
|
||||
Future<void> checkForUpdates(BuildContext context) async {
|
||||
log('[Update] Checking for updates...');
|
||||
try {
|
||||
final release = await fetchLatestRelease();
|
||||
if (release == null) {
|
||||
log('[Update] No latest release found or could not fetch.');
|
||||
return;
|
||||
}
|
||||
log('[Update] Fetched latest release: ${release.tagName}');
|
||||
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
final localVersionStr = '${info.version}+${info.buildNumber}';
|
||||
log('[Update] Local app version: $localVersionStr');
|
||||
|
||||
final latest = _ParsedVersion.tryParse(release.tagName);
|
||||
final local = _ParsedVersion.tryParse(localVersionStr);
|
||||
|
||||
if (latest == null || local == null) {
|
||||
log(
|
||||
'[Update] Failed to parse versions. Latest: ${release.tagName}, Local: $localVersionStr',
|
||||
);
|
||||
// If parsing fails, do nothing silently
|
||||
return;
|
||||
}
|
||||
log('[Update] Parsed versions. Latest: $latest, Local: $local');
|
||||
|
||||
final needsUpdate = latest.compareTo(local) > 0;
|
||||
if (!needsUpdate) {
|
||||
log('[Update] App is up to date. No update needed.');
|
||||
return;
|
||||
}
|
||||
log('[Update] Update available! Latest: $latest, Local: $local');
|
||||
|
||||
if (!context.mounted) {
|
||||
log('[Update] Context not mounted, cannot show update sheet.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay to ensure UI is ready (if called at startup)
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
if (context.mounted) {
|
||||
await showUpdateSheet(context, release);
|
||||
log('[Update] Update sheet shown.');
|
||||
}
|
||||
} catch (e) {
|
||||
log('[Update] Error checking for updates: $e');
|
||||
// Ignore errors (network, api, etc.)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually show the update sheet with a provided release.
|
||||
/// Useful for About page or testing.
|
||||
Future<void> showUpdateSheet(
|
||||
BuildContext context,
|
||||
GithubReleaseInfo release,
|
||||
) async {
|
||||
if (!context.mounted) return;
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useRootNavigator: true,
|
||||
builder: (ctx) {
|
||||
String? androidUpdateUrl;
|
||||
if (Platform.isAndroid) {
|
||||
androidUpdateUrl = _getAndroidUpdateUrl(release.assets);
|
||||
}
|
||||
return _UpdateSheet(
|
||||
release: release,
|
||||
onOpen: () async {
|
||||
final uri = Uri.parse(release.htmlUrl);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
},
|
||||
androidUpdateUrl: androidUpdateUrl,
|
||||
useProxy: useProxy, // Pass the useProxy flag
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String? _getAndroidUpdateUrl(List<GithubReleaseAsset> assets) {
|
||||
final arm64 = assets.firstWhereOrNull(
|
||||
(asset) => asset.name == 'app-arm64-v8a-release.apk',
|
||||
);
|
||||
final armeabi = assets.firstWhereOrNull(
|
||||
(asset) => asset.name == 'app-armeabi-v7a-release.apk',
|
||||
);
|
||||
final x86_64 = assets.firstWhereOrNull(
|
||||
(asset) => asset.name == 'app-x86_64-release.apk',
|
||||
);
|
||||
|
||||
// Prioritize arm64, then armeabi, then x86_64
|
||||
if (arm64 != null) {
|
||||
return arm64.browserDownloadUrl;
|
||||
} else if (armeabi != null) {
|
||||
return armeabi.browserDownloadUrl;
|
||||
} else if (x86_64 != null) {
|
||||
return x86_64.browserDownloadUrl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Fetch the latest release info from GitHub.
|
||||
/// Public so other screens (e.g., About) can manually trigger update checks.
|
||||
Future<GithubReleaseInfo?> fetchLatestRelease() async {
|
||||
final apiEndpoint =
|
||||
useProxy
|
||||
? '$_proxyBaseUrl${Uri.encodeComponent(_releasesLatestApi)}'
|
||||
: _releasesLatestApi;
|
||||
|
||||
log(
|
||||
'[Update] Fetching latest release from GitHub API: $apiEndpoint (Proxy: $useProxy)',
|
||||
);
|
||||
final resp = await _dio.get(apiEndpoint);
|
||||
if (resp.statusCode != 200) {
|
||||
log(
|
||||
'[Update] Failed to fetch latest release. Status code: ${resp.statusCode}',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
final data = resp.data as Map<String, dynamic>;
|
||||
log('[Update] Successfully fetched release data.');
|
||||
|
||||
final tagName = (data['tag_name'] ?? '').toString();
|
||||
final name = (data['name'] ?? tagName).toString();
|
||||
final body = (data['body'] ?? '').toString();
|
||||
final htmlUrl = (data['html_url'] ?? '').toString();
|
||||
final createdAtStr = (data['created_at'] ?? '').toString();
|
||||
final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now();
|
||||
final assetsData =
|
||||
(data['assets'] as List<dynamic>?)
|
||||
?.map((e) => GithubReleaseAsset.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
if (tagName.isEmpty || htmlUrl.isEmpty) {
|
||||
log(
|
||||
'[Update] Missing tag_name or html_url in release data. TagName: "$tagName", HtmlUrl: "$htmlUrl"',
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
log('[Update] Returning GithubReleaseInfo for tag: $tagName');
|
||||
return GithubReleaseInfo(
|
||||
tagName: tagName,
|
||||
name: name,
|
||||
body: body,
|
||||
htmlUrl: htmlUrl,
|
||||
createdAt: createdAt,
|
||||
assets: assetsData,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UpdateSheet extends StatefulWidget {
|
||||
const _UpdateSheet({
|
||||
required this.release,
|
||||
required this.onOpen,
|
||||
this.androidUpdateUrl,
|
||||
this.useProxy = false,
|
||||
});
|
||||
|
||||
final String? androidUpdateUrl;
|
||||
final bool useProxy;
|
||||
final GithubReleaseInfo release;
|
||||
final VoidCallback onOpen;
|
||||
|
||||
@override
|
||||
State<_UpdateSheet> createState() => _UpdateSheetState();
|
||||
}
|
||||
|
||||
class _UpdateSheetState extends State<_UpdateSheet> {
|
||||
late bool _useProxy;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_useProxy = widget.useProxy;
|
||||
}
|
||||
|
||||
Future<void> _installUpdate(String url) async {
|
||||
final downloadUrl =
|
||||
_useProxy ? 'https://ghfast.top/${Uri.encodeComponent(url)}' : url;
|
||||
|
||||
UpdateModel model = UpdateModel(
|
||||
downloadUrl,
|
||||
"solian-update-${widget.release.tagName}.apk",
|
||||
"launcher_icon",
|
||||
'https://apps.apple.com/us/app/solian/id6499032345',
|
||||
);
|
||||
AzhonAppUpdate.update(model);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return SheetScaffold(
|
||||
titleText: 'Update available',
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 16 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.release.name,
|
||||
style: theme.textTheme.titleMedium,
|
||||
).bold(),
|
||||
Text(widget.release.tagName).fontSize(12),
|
||||
],
|
||||
).padding(vertical: 16, horizontal: 16),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
child: MarkdownTextContent(
|
||||
content:
|
||||
widget.release.body.isEmpty
|
||||
? 'No changelog provided.'
|
||||
: widget.release.body,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!kIsWeb && Platform.isAndroid)
|
||||
SwitchListTile(
|
||||
title: const Text('Use GitHub Proxy for Download'),
|
||||
value: _useProxy,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_useProxy = value;
|
||||
});
|
||||
},
|
||||
).padding(horizontal: 8),
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (!kIsWeb &&
|
||||
Platform.isAndroid &&
|
||||
widget.androidUpdateUrl != null)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
log(widget.androidUpdateUrl!);
|
||||
_installUpdate(widget.androidUpdateUrl!);
|
||||
},
|
||||
icon: const Icon(Symbols.update),
|
||||
label: const Text('Install update'),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: widget.onOpen,
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
label: const Text('Open release page'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
30
lib/utils/mapping.dart
Normal file
30
lib/utils/mapping.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
String _upperCamelToLowerSnake(String input) {
|
||||
final regex = RegExp(r'(?<=[a-z0-9])([A-Z])');
|
||||
return input
|
||||
.replaceAllMapped(regex, (match) => '_${match.group(0)}')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
Map<String, dynamic> convertMapKeysToSnakeCase(Map<String, dynamic> input) {
|
||||
final result = <String, dynamic>{};
|
||||
|
||||
input.forEach((key, value) {
|
||||
final newKey = _upperCamelToLowerSnake(key);
|
||||
|
||||
if (value is Map<String, dynamic>) {
|
||||
result[newKey] = convertMapKeysToSnakeCase(value);
|
||||
} else if (value is List) {
|
||||
result[newKey] =
|
||||
value.map((item) {
|
||||
if (item is Map<String, dynamic>) {
|
||||
return convertMapKeysToSnakeCase(item);
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
} else {
|
||||
result[newKey] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
@@ -130,9 +130,22 @@ class AccountStatusWidget extends HookConsumerWidget {
|
||||
size: 16,
|
||||
).padding(right: 4),
|
||||
if (status.value?.isCustomized ?? false)
|
||||
Text(status.value?.label ?? 'unknown'.tr())
|
||||
Flexible(
|
||||
child: Text(
|
||||
status.value?.label ?? 'unknown'.tr(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
else
|
||||
Text((status.value?.label ?? 'offline').toLowerCase()).tr(),
|
||||
Flexible(
|
||||
child:
|
||||
Text(
|
||||
(status.value?.label ?? 'offline').toLowerCase(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).tr(),
|
||||
),
|
||||
if (!(status.value?.isOnline ?? false) &&
|
||||
account.value?.profile.lastSeenAt != null)
|
||||
Flexible(
|
||||
|
@@ -55,7 +55,7 @@ class AccountStatusCreationSheet extends HookConsumerWidget {
|
||||
'attitude': attitude.value,
|
||||
'is_invisible': isInvisible.value,
|
||||
'is_not_disturb': isNotDisturb.value,
|
||||
'cleared_at': clearedAt.value?.toIso8601String(),
|
||||
'cleared_at': clearedAt.value?.toUtc().toIso8601String(),
|
||||
if (labelController.text.isNotEmpty) 'label': labelController.text,
|
||||
},
|
||||
options: Options(method: initialStatus == null ? 'POST' : 'PATCH'),
|
||||
|
@@ -331,7 +331,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
||||
final user = ref.watch(userInfoProvider);
|
||||
final websocketState = ref.watch(websocketStateProvider);
|
||||
final indicatorHeight =
|
||||
MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 60);
|
||||
MediaQuery.of(context).padding.top + (isDesktop ? 27.5 : 25);
|
||||
|
||||
Color indicatorColor;
|
||||
String indicatorText;
|
||||
@@ -343,7 +343,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
||||
indicatorColor = Colors.teal;
|
||||
indicatorText = 'connectionReconnecting';
|
||||
} else {
|
||||
indicatorColor = Colors.orange;
|
||||
indicatorColor = Colors.red;
|
||||
indicatorText = 'connectionDisconnected';
|
||||
}
|
||||
|
||||
|
@@ -2,8 +2,11 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/services/notify.dart';
|
||||
import 'package:island/services/sharing_intent.dart';
|
||||
import 'package:island/services/update_service.dart';
|
||||
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||
import 'package:island/widgets/tour/tour.dart';
|
||||
|
||||
class AppWrapper extends HookConsumerWidget {
|
||||
@@ -19,12 +22,34 @@ class AppWrapper extends HookConsumerWidget {
|
||||
});
|
||||
final sharingService = SharingIntentService();
|
||||
sharingService.initialize(context);
|
||||
UpdateService().checkForUpdates(context);
|
||||
return () {
|
||||
sharingService.dispose();
|
||||
ntySubs?.cancel();
|
||||
};
|
||||
}, const []);
|
||||
|
||||
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||
final websocketState = ref.watch(websocketStateProvider);
|
||||
|
||||
final networkStateShowing = useState(false);
|
||||
|
||||
if (websocketState == WebSocketState.duplicateDevice()) {
|
||||
if (!networkStateShowing.value) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
networkStateShowing.value = true;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
isDismissible: false,
|
||||
builder:
|
||||
(context) =>
|
||||
NetworkStatusSheet(onReconnect: () => wsNotifier.connect()),
|
||||
).then((_) => networkStateShowing.value = false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return TourTriggerWidget(child: child);
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import 'package:island/models/embed.dart';
|
||||
import 'package:island/pods/call.dart';
|
||||
import 'package:island/pods/translate.dart';
|
||||
import 'package:island/screens/chat/room.dart';
|
||||
import 'package:island/utils/mapping.dart';
|
||||
import 'package:island/widgets/account/account_name.dart';
|
||||
import 'package:island/widgets/account/account_pfc.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
@@ -292,12 +293,11 @@ class MessageItem extends HookConsumerWidget {
|
||||
),
|
||||
if (remoteMessage.meta['embeds'] != null)
|
||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||
.where((embed) => embed['Type'] == 'link')
|
||||
.map(
|
||||
(embed) => SnEmbedLink.fromJson(
|
||||
embed as Map<String, dynamic>,
|
||||
),
|
||||
(embed) => convertMapKeysToSnakeCase(embed),
|
||||
)
|
||||
.where((embed) => embed['type'] == 'link')
|
||||
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||
.map(
|
||||
(link) => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
|
@@ -36,7 +36,8 @@ Future<SnCheckInResult?> checkInResultToday(Ref ref) async {
|
||||
|
||||
class CheckInWidget extends HookConsumerWidget {
|
||||
final EdgeInsets? margin;
|
||||
const CheckInWidget({super.key, this.margin});
|
||||
final VoidCallback? onChecked;
|
||||
const CheckInWidget({super.key, this.margin, this.onChecked});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -52,6 +53,7 @@ class CheckInWidget extends HookConsumerWidget {
|
||||
ref.invalidate(checkInResultTodayProvider);
|
||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||
userNotifier.fetchUser();
|
||||
onChecked?.call();
|
||||
} catch (err) {
|
||||
if (err is DioException) {
|
||||
if (err.response?.statusCode == 423 && context.mounted) {
|
||||
|
@@ -272,8 +272,96 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: Column(
|
||||
child: Stack(
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: ratio,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Builder(
|
||||
key: ValueKey(item.hashCode),
|
||||
builder: (context) {
|
||||
if (item.isOnCloud) {
|
||||
return CloudFileWidget(item: item.data);
|
||||
} else if (item.data is XFile) {
|
||||
final file = item.data as XFile;
|
||||
if (file.path.isEmpty) {
|
||||
return FutureBuilder<Uint8List>(
|
||||
future: file.readAsBytes(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Image.memory(snapshot.data!);
|
||||
}
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
switch (item.type) {
|
||||
case UniversalFileType.image:
|
||||
return kIsWeb
|
||||
? Image.network(file.path)
|
||||
: Image.file(File(file.path));
|
||||
default:
|
||||
return Column(
|
||||
children: [
|
||||
const Icon(Symbols.document_scanner),
|
||||
Text(file.name),
|
||||
],
|
||||
);
|
||||
}
|
||||
} else if (item is List<int> || item is Uint8List) {
|
||||
switch (item.type) {
|
||||
case UniversalFileType.image:
|
||||
return Image.memory(item.data);
|
||||
default:
|
||||
return Column(
|
||||
children: [const Icon(Symbols.document_scanner)],
|
||||
);
|
||||
}
|
||||
}
|
||||
return Placeholder();
|
||||
},
|
||||
),
|
||||
if (progress != null)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 40,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (progress != null)
|
||||
Text(
|
||||
'${progress!.toStringAsFixed(2)}%',
|
||||
style: TextStyle(color: Colors.white),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
'uploading'.tr(),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
Gap(6),
|
||||
Center(
|
||||
child: LinearProgressIndicator(
|
||||
value:
|
||||
progress != null ? progress! / 100.0 : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -397,94 +485,6 @@ class AttachmentPreview extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 12, vertical: 8),
|
||||
AspectRatio(
|
||||
aspectRatio: ratio,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Builder(
|
||||
key: ValueKey(item.hashCode),
|
||||
builder: (context) {
|
||||
if (item.isOnCloud) {
|
||||
return CloudFileWidget(item: item.data);
|
||||
} else if (item.data is XFile) {
|
||||
final file = item.data as XFile;
|
||||
if (file.path.isEmpty) {
|
||||
return FutureBuilder<Uint8List>(
|
||||
future: file.readAsBytes(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Image.memory(snapshot.data!);
|
||||
}
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
switch (item.type) {
|
||||
case UniversalFileType.image:
|
||||
return kIsWeb
|
||||
? Image.network(file.path)
|
||||
: Image.file(File(file.path));
|
||||
default:
|
||||
return Column(
|
||||
children: [
|
||||
const Icon(Symbols.document_scanner),
|
||||
Text(file.name),
|
||||
],
|
||||
);
|
||||
}
|
||||
} else if (item is List<int> || item is Uint8List) {
|
||||
switch (item.type) {
|
||||
case UniversalFileType.image:
|
||||
return Image.memory(item.data);
|
||||
default:
|
||||
return Column(
|
||||
children: [const Icon(Symbols.document_scanner)],
|
||||
);
|
||||
}
|
||||
}
|
||||
return Placeholder();
|
||||
},
|
||||
),
|
||||
if (progress != null)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 40,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (progress != null)
|
||||
Text(
|
||||
'${progress!.toStringAsFixed(2)}%',
|
||||
style: TextStyle(color: Colors.white),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
'uploading'.tr(),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
Gap(6),
|
||||
Center(
|
||||
child: LinearProgressIndicator(
|
||||
value:
|
||||
progress != null ? progress! / 100.0 : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -103,6 +102,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
||||
Symbols.play_arrow,
|
||||
fill: 1,
|
||||
size: 32,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
BoxShadow(
|
||||
color: Colors.black54,
|
||||
@@ -114,6 +114,26 @@ class CloudVideoWidget extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface.withOpacity(0.85),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
@@ -122,7 +142,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (item.fileMeta?['duration'] != null)
|
||||
@@ -133,6 +153,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
||||
.toInt(),
|
||||
).formatDuration(),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
BoxShadow(
|
||||
color: Colors.black54,
|
||||
@@ -147,6 +168,7 @@ class CloudVideoWidget extends HookConsumerWidget {
|
||||
Text(
|
||||
'${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
BoxShadow(
|
||||
color: Colors.black54,
|
||||
@@ -161,7 +183,10 @@ class CloudVideoWidget extends HookConsumerWidget {
|
||||
),
|
||||
Text(
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
shadows: [
|
||||
BoxShadow(
|
||||
@@ -174,8 +199,8 @@ class CloudVideoWidget extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).padding(horizontal: 16, bottom: 12),
|
||||
).padding(horizontal: 16, bottom: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
|
@@ -6,7 +6,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class EmbedLinkWidget extends StatelessWidget {
|
||||
final SnEmbedLink link;
|
||||
final SnScrappedLink link;
|
||||
final double? maxWidth;
|
||||
final EdgeInsetsGeometry? margin;
|
||||
|
||||
@@ -116,7 +116,8 @@ class EmbedLinkWidget extends StatelessWidget {
|
||||
],
|
||||
|
||||
// Description
|
||||
if (link.description != null && link.description!.isNotEmpty) ...[
|
||||
if (link.description != null &&
|
||||
link.description!.isNotEmpty) ...[
|
||||
Text(
|
||||
link.description!,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
@@ -191,7 +192,7 @@ class EmbedLinkWidget extends StatelessWidget {
|
||||
try {
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(date);
|
||||
|
||||
|
||||
if (difference.inDays == 0) {
|
||||
return 'Today';
|
||||
} else if (difference.inDays == 1) {
|
||||
|
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_highlight/themes/a11y-dark.dart';
|
||||
import 'package:flutter_highlight/themes/a11y-light.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
@@ -71,7 +72,22 @@ class MarkdownTextContent extends HookConsumerWidget {
|
||||
textStyle: textStyle ?? Theme.of(context).textTheme.bodyMedium!,
|
||||
),
|
||||
HrConfig(height: 1, color: Theme.of(context).dividerColor),
|
||||
PreConfig(theme: isDark ? a11yDarkTheme : a11yLightTheme),
|
||||
PreConfig(
|
||||
theme: isDark ? a11yDarkTheme : a11yLightTheme,
|
||||
textStyle: GoogleFonts.robotoMono(fontSize: 14),
|
||||
styleNotMatched: GoogleFonts.robotoMono(fontSize: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
||||
),
|
||||
),
|
||||
TableConfig(
|
||||
wrapper:
|
||||
(child) => SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
LinkConfig(
|
||||
style:
|
||||
linkStyle ??
|
||||
@@ -145,6 +161,8 @@ class MarkdownTextContent extends HookConsumerWidget {
|
||||
);
|
||||
case 'stickers':
|
||||
final size = doesEnlargeSticker ? 96.0 : 24.0;
|
||||
final stickerUri =
|
||||
'$baseUrl/sphere/stickers/lookup/${uri.pathSegments[0]}/open';
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
@@ -155,11 +173,10 @@ class MarkdownTextContent extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
child: UniversalImage(
|
||||
uri:
|
||||
'$baseUrl/sphere/stickers/lookup/${uri.pathSegments[0]}/open',
|
||||
uri: stickerUri,
|
||||
width: size,
|
||||
height: size,
|
||||
fit: BoxFit.cover,
|
||||
fit: BoxFit.contain,
|
||||
noCacheOptimization: true,
|
||||
),
|
||||
),
|
||||
|
81
lib/widgets/content/network_status_sheet.dart
Normal file
81
lib/widgets/content/network_status_sheet.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
|
||||
class NetworkStatusSheet extends HookConsumerWidget {
|
||||
final VoidCallback onReconnect;
|
||||
|
||||
const NetworkStatusSheet({super.key, required this.onReconnect});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final ws = ref.watch(websocketProvider);
|
||||
final wsState = ref.watch(websocketStateProvider);
|
||||
|
||||
return SheetScaffold(
|
||||
titleText:
|
||||
wsState == WebSocketState.connected()
|
||||
? 'Connection Status'
|
||||
: 'Connection Issue',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
wsState.when(
|
||||
connected:
|
||||
() => Text(
|
||||
'Connected to server',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
connecting:
|
||||
() => Text(
|
||||
'Connecting to server...',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
disconnected:
|
||||
() => Text(
|
||||
'Disconnected from server',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
serverDown:
|
||||
() => Text(
|
||||
'The server is not available right now... Please try again later...',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
duplicateDevice:
|
||||
() => Text(
|
||||
'Another device has connected with the same account.',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
error:
|
||||
(message) => Text(
|
||||
'Connection error: $message',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (ws.heartbeatDelay != null)
|
||||
Text(
|
||||
'Last heartbeat: ${ws.heartbeatDelay!.inMilliseconds}ms',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Center(
|
||||
child: FilledButton.icon(
|
||||
icon: const Icon(Symbols.wifi),
|
||||
label: const Text('Reconnect'),
|
||||
onPressed: () {
|
||||
onReconnect();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
76
lib/widgets/debug_sheet.dart
Normal file
76
lib/widgets/debug_sheet.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/pods/message.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/widgets/content/network_status_sheet.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class DebugSheet extends HookConsumerWidget {
|
||||
const DebugSheet({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final wsNotifier = ref.watch(websocketStateProvider.notifier);
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: 'Debug',
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.wifi),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
title: Text('Connection Status'),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder:
|
||||
(context) => NetworkStatusSheet(
|
||||
onReconnect: () => wsNotifier.connect(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.copy_all),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('Copy access token'),
|
||||
onTap: () async {
|
||||
final tk = ref.watch(tokenProvider);
|
||||
Clipboard.setData(ClipboardData(text: tk!.token));
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.delete),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('Reset database'),
|
||||
onTap: () async {
|
||||
resetDatabase(ref);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.clear),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||
title: Text('Clear cache'),
|
||||
onTap: () async {
|
||||
DefaultCacheManager().emptyCache();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -248,7 +248,7 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> {
|
||||
try {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final response = await client.post(
|
||||
'/orders/${widget.order.id}/pay',
|
||||
'/id/orders/${widget.order.id}/pay',
|
||||
data: {'pin_code': pin},
|
||||
);
|
||||
|
||||
|
236
lib/widgets/poll/poll_feedback.dart
Normal file
236
lib/widgets/poll/poll_feedback.dart
Normal file
@@ -0,0 +1,236 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/poll.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'poll_feedback.g.dart';
|
||||
|
||||
@riverpod
|
||||
class PollFeedbackNotifier extends _$PollFeedbackNotifier
|
||||
with CursorPagingNotifierMixin<SnPollAnswer> {
|
||||
static const int _pageSize = 20;
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<SnPollAnswer>> build(String id) {
|
||||
// immediately load first page
|
||||
return fetch(cursor: null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<SnPollAnswer>> fetch({
|
||||
required String? cursor,
|
||||
}) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||
|
||||
final queryParams = {'offset': offset, 'take': _pageSize};
|
||||
|
||||
final response = await client.get(
|
||||
'/sphere/polls/$id/feedback',
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||
final List<dynamic> data = response.data;
|
||||
final items = data.map((json) => SnPollAnswer.fromJson(json)).toList();
|
||||
|
||||
final hasMore = offset + items.length < total;
|
||||
final nextCursor = hasMore ? (offset + items.length).toString() : null;
|
||||
|
||||
return CursorPagingData(
|
||||
items: items,
|
||||
hasMore: hasMore,
|
||||
nextCursor: nextCursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PollFeedbackSheet extends HookConsumerWidget {
|
||||
final String pollId;
|
||||
final String? title;
|
||||
final SnPoll poll;
|
||||
final Map<String, dynamic>? stats; // stats object similar to PollSubmit
|
||||
const PollFeedbackSheet({
|
||||
super.key,
|
||||
required this.pollId,
|
||||
required this.poll,
|
||||
this.title,
|
||||
this.stats,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return SheetScaffold(
|
||||
titleText: title ?? 'Poll feedback',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_PollHeader(poll: poll, stats: stats),
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: PagingHelperView(
|
||||
provider: pollFeedbackNotifierProvider(pollId),
|
||||
futureRefreshable: pollFeedbackNotifierProvider(pollId).future,
|
||||
notifierRefreshable:
|
||||
pollFeedbackNotifierProvider(pollId).notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) => ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
itemCount: widgetCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == widgetCount - 1) {
|
||||
// Provided by PagingHelperView to indicate end/loading
|
||||
return endItemView;
|
||||
}
|
||||
final answer = data.items[index];
|
||||
return _PollAnswerTile(answer: answer, poll: poll);
|
||||
},
|
||||
separatorBuilder:
|
||||
(context, index) =>
|
||||
const Divider(height: 1).padding(vertical: 4),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PollHeader extends StatelessWidget {
|
||||
const _PollHeader({required this.poll, this.stats});
|
||||
final SnPoll poll;
|
||||
final Map<String, dynamic>? stats;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (poll.title != null)
|
||||
Text(poll.title!, style: theme.textTheme.titleLarge),
|
||||
if (poll.description != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
poll.description!,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.textTheme.bodyMedium?.color?.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 16);
|
||||
}
|
||||
}
|
||||
|
||||
class _PollAnswerTile extends StatelessWidget {
|
||||
final SnPollAnswer answer;
|
||||
final SnPoll poll;
|
||||
const _PollAnswerTile({required this.answer, required this.poll});
|
||||
|
||||
String _formatPerQuestionAnswer(
|
||||
SnPollQuestion q,
|
||||
Map<String, dynamic> ansMap,
|
||||
) {
|
||||
switch (q.type) {
|
||||
case SnPollQuestionType.singleChoice:
|
||||
final val = ansMap[q.id];
|
||||
if (val is String) {
|
||||
final opt = q.options?.firstWhere(
|
||||
(o) => o.id == val,
|
||||
orElse: () => SnPollOption(id: val, label: '#$val', order: 0),
|
||||
);
|
||||
return opt?.label ?? '#$val';
|
||||
}
|
||||
return '—';
|
||||
case SnPollQuestionType.multipleChoice:
|
||||
final val = ansMap[q.id];
|
||||
if (val is List) {
|
||||
final ids = val.whereType<String>().toList();
|
||||
if (ids.isEmpty) return '—';
|
||||
final labels =
|
||||
ids.map((id) {
|
||||
final opt = q.options?.firstWhere(
|
||||
(o) => o.id == id,
|
||||
orElse: () => SnPollOption(id: id, label: '#$id', order: 0),
|
||||
);
|
||||
return opt?.label ?? '#$id';
|
||||
}).toList();
|
||||
return labels.join(', ');
|
||||
}
|
||||
return '—';
|
||||
case SnPollQuestionType.yesNo:
|
||||
final val = ansMap[q.id];
|
||||
if (val is bool) {
|
||||
return val ? 'Yes' : 'No';
|
||||
}
|
||||
return '—';
|
||||
case SnPollQuestionType.rating:
|
||||
final val = ansMap[q.id];
|
||||
if (val is int) return val.toString();
|
||||
if (val is num) return val.toString();
|
||||
return '—';
|
||||
case SnPollQuestionType.freeText:
|
||||
final val = ansMap[q.id];
|
||||
if (val is String && val.trim().isNotEmpty) return val;
|
||||
return '—';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Submit date/time (title)
|
||||
final submitText = answer.createdAt.formatSystem();
|
||||
|
||||
// Compose content from poll questions if provided, otherwise fallback to joined key-values
|
||||
String content;
|
||||
if (poll.questions.isNotEmpty) {
|
||||
final questions = [...poll.questions]
|
||||
..sort((a, b) => a.order.compareTo(b.order));
|
||||
final buffer = StringBuffer();
|
||||
for (final q in questions) {
|
||||
final formatted = _formatPerQuestionAnswer(q, answer.answer);
|
||||
buffer.writeln('${q.title}: $formatted');
|
||||
}
|
||||
content = buffer.toString().trimRight();
|
||||
} else {
|
||||
// Fallback formatting without poll context. We still want to show the question title
|
||||
// instead of the raw question id key if we can derive it from the answer map itself.
|
||||
// Since we don't have poll metadata here, we cannot resolve the title; therefore we
|
||||
// will show only values line-by-line without exposing the raw id.
|
||||
if (answer.answer.isEmpty) {
|
||||
content = '—';
|
||||
} else {
|
||||
final parts = <String>[];
|
||||
answer.answer.forEach((key, value) {
|
||||
var question = poll.questions.firstWhere((q) => q.id == key);
|
||||
if (value is List) {
|
||||
parts.add('${question.title}: ${value.join(', ')}');
|
||||
} else {
|
||||
parts.add('${question.title}: $value');
|
||||
}
|
||||
});
|
||||
content = parts.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
isThreeLine: true,
|
||||
leading: const CircleAvatar(
|
||||
radius: 16,
|
||||
child: Icon(Icons.how_to_vote, size: 16),
|
||||
),
|
||||
title: Text(submitText),
|
||||
subtitle: Text(content),
|
||||
trailing: null,
|
||||
);
|
||||
}
|
||||
}
|
180
lib/widgets/poll/poll_feedback.g.dart
Normal file
180
lib/widgets/poll/poll_feedback.g.dart
Normal file
@@ -0,0 +1,180 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'poll_feedback.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$pollFeedbackNotifierHash() =>
|
||||
r'1bf3925b5b751cfd1a9abafb75274f1e95e7f27e';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$PollFeedbackNotifier
|
||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPollAnswer>> {
|
||||
late final String id;
|
||||
|
||||
FutureOr<CursorPagingData<SnPollAnswer>> build(String id);
|
||||
}
|
||||
|
||||
/// See also [PollFeedbackNotifier].
|
||||
@ProviderFor(PollFeedbackNotifier)
|
||||
const pollFeedbackNotifierProvider = PollFeedbackNotifierFamily();
|
||||
|
||||
/// See also [PollFeedbackNotifier].
|
||||
class PollFeedbackNotifierFamily
|
||||
extends Family<AsyncValue<CursorPagingData<SnPollAnswer>>> {
|
||||
/// See also [PollFeedbackNotifier].
|
||||
const PollFeedbackNotifierFamily();
|
||||
|
||||
/// See also [PollFeedbackNotifier].
|
||||
PollFeedbackNotifierProvider call(String id) {
|
||||
return PollFeedbackNotifierProvider(id);
|
||||
}
|
||||
|
||||
@override
|
||||
PollFeedbackNotifierProvider getProviderOverride(
|
||||
covariant PollFeedbackNotifierProvider provider,
|
||||
) {
|
||||
return call(provider.id);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'pollFeedbackNotifierProvider';
|
||||
}
|
||||
|
||||
/// See also [PollFeedbackNotifier].
|
||||
class PollFeedbackNotifierProvider
|
||||
extends
|
||||
AutoDisposeAsyncNotifierProviderImpl<
|
||||
PollFeedbackNotifier,
|
||||
CursorPagingData<SnPollAnswer>
|
||||
> {
|
||||
/// See also [PollFeedbackNotifier].
|
||||
PollFeedbackNotifierProvider(String id)
|
||||
: this._internal(
|
||||
() => PollFeedbackNotifier()..id = id,
|
||||
from: pollFeedbackNotifierProvider,
|
||||
name: r'pollFeedbackNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$pollFeedbackNotifierHash,
|
||||
dependencies: PollFeedbackNotifierFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
PollFeedbackNotifierFamily._allTransitiveDependencies,
|
||||
id: id,
|
||||
);
|
||||
|
||||
PollFeedbackNotifierProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.id,
|
||||
}) : super.internal();
|
||||
|
||||
final String id;
|
||||
|
||||
@override
|
||||
FutureOr<CursorPagingData<SnPollAnswer>> runNotifierBuild(
|
||||
covariant PollFeedbackNotifier notifier,
|
||||
) {
|
||||
return notifier.build(id);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(PollFeedbackNotifier Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PollFeedbackNotifierProvider._internal(
|
||||
() => create()..id = id,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
id: id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeAsyncNotifierProviderElement<
|
||||
PollFeedbackNotifier,
|
||||
CursorPagingData<SnPollAnswer>
|
||||
>
|
||||
createElement() {
|
||||
return _PollFeedbackNotifierProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PollFeedbackNotifierProvider && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, id.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PollFeedbackNotifierRef
|
||||
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPollAnswer>> {
|
||||
/// The parameter `id` of this provider.
|
||||
String get id;
|
||||
}
|
||||
|
||||
class _PollFeedbackNotifierProviderElement
|
||||
extends
|
||||
AutoDisposeAsyncNotifierProviderElement<
|
||||
PollFeedbackNotifier,
|
||||
CursorPagingData<SnPollAnswer>
|
||||
>
|
||||
with PollFeedbackNotifierRef {
|
||||
_PollFeedbackNotifierProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get id => (origin as PollFeedbackNotifierProvider).id;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
@@ -1,23 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:island/models/poll.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
|
||||
/// A poll answering widget that shows one question at a time and collects answers.
|
||||
///
|
||||
/// Usage:
|
||||
/// PollSubmit(
|
||||
/// poll: poll,
|
||||
/// onSubmit: (answers) {
|
||||
/// // answers is Map<String, dynamic>: questionId -> answer
|
||||
/// // answer types by question:
|
||||
/// // - singleChoice: String optionId
|
||||
/// // - multipleChoice: List<String> optionIds
|
||||
/// // - yesNo: bool
|
||||
/// // - rating: int (1..5)
|
||||
/// // - freeText: String
|
||||
/// },
|
||||
/// )
|
||||
class PollSubmit extends ConsumerStatefulWidget {
|
||||
const PollSubmit({
|
||||
super.key,
|
||||
@@ -208,12 +195,11 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
|
||||
|
||||
// Only call onSubmit after server accepts
|
||||
widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers));
|
||||
|
||||
showSnackBar('Poll answer has been submitted.');
|
||||
HapticFeedback.heavyImpact();
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Failed to submit poll: $e')));
|
||||
}
|
||||
showErrorAlert(e);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
@@ -189,8 +189,8 @@ class ComposePollSheet extends HookConsumerWidget {
|
||||
Widget? _buildPollSubtitle(SnPoll poll) {
|
||||
try {
|
||||
final SnPoll dyn = poll;
|
||||
final List<SnPollQuestion>? options = dyn.questions;
|
||||
if (options == null || options.isEmpty) return null;
|
||||
final List<SnPollQuestion> options = dyn.questions;
|
||||
if (options.isEmpty) return null;
|
||||
final preview = options.take(3).map((e) => e.title).join(' · ');
|
||||
if (preview.trim().isEmpty) return null;
|
||||
return Text(preview);
|
||||
|
@@ -1,11 +1,29 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
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_category.dart';
|
||||
import 'package:island/pods/network.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:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:textfield_tags/textfield_tags.dart';
|
||||
|
||||
part 'compose_settings_sheet.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<SnPostCategory>> postCategories(Ref ref) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/posts/categories');
|
||||
return resp.data
|
||||
.map((e) => SnPostCategory.fromJson(e))
|
||||
.cast<SnPostCategory>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// A reusable widget for tag input fields with chip display
|
||||
class ChipTagInputField extends StatelessWidget {
|
||||
final InputFieldValues inputFieldValues;
|
||||
@@ -98,31 +116,20 @@ class ChipTagInputField extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ComposeSettingsSheet extends HookWidget {
|
||||
final TextEditingController titleController;
|
||||
final TextEditingController descriptionController;
|
||||
final ValueNotifier<int> visibility;
|
||||
final VoidCallback? onVisibilityChanged;
|
||||
final StringTagController tagsController;
|
||||
final StringTagController categoriesController;
|
||||
class ComposeSettingsSheet extends HookConsumerWidget {
|
||||
final ComposeState state;
|
||||
|
||||
const ComposeSettingsSheet({
|
||||
super.key,
|
||||
required this.titleController,
|
||||
required this.descriptionController,
|
||||
required this.visibility,
|
||||
this.onVisibilityChanged,
|
||||
required this.tagsController,
|
||||
required this.categoriesController,
|
||||
});
|
||||
const ComposeSettingsSheet({super.key, required this.state});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
// Listen to visibility changes to trigger rebuilds
|
||||
final currentVisibility = useValueListenable(visibility);
|
||||
final currentVisibility = useValueListenable(state.visibility);
|
||||
final currentCategories = useValueListenable(state.categories);
|
||||
final postCategories = ref.watch(postCategoriesProvider);
|
||||
|
||||
IconData getVisibilityIcon(int visibilityValue) {
|
||||
switch (visibilityValue) {
|
||||
@@ -160,11 +167,10 @@ class ComposeSettingsSheet extends HookWidget {
|
||||
leading: Icon(icon),
|
||||
title: Text(textKey.tr()),
|
||||
onTap: () {
|
||||
visibility.value = value;
|
||||
onVisibilityChanged?.call();
|
||||
state.visibility.value = value;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
selected: visibility.value == value,
|
||||
selected: state.visibility.value == value,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
);
|
||||
}
|
||||
@@ -210,48 +216,16 @@ class ComposeSettingsSheet extends HookWidget {
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: 'postSettings'.tr(),
|
||||
heightFactor: 0.6,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 16,
|
||||
children: [
|
||||
// Title field
|
||||
TextField(
|
||||
controller: titleController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'postTitle'.tr(),
|
||||
hintText: 'postTitle'.tr(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
),
|
||||
style: theme.textTheme.titleMedium,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
|
||||
// Description field
|
||||
TextField(
|
||||
controller: descriptionController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'postDescription'.tr(),
|
||||
hintText: 'postDescription'.tr(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
maxLines: 3,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
|
||||
// Tags field
|
||||
TextFieldTags(
|
||||
textfieldTagsController: tagsController,
|
||||
textfieldTagsController: state.tagsController,
|
||||
textSeparators: const [' ', ','],
|
||||
letterCase: LetterCase.normal,
|
||||
validator: (String tag) {
|
||||
@@ -270,22 +244,105 @@ class ComposeSettingsSheet extends HookWidget {
|
||||
),
|
||||
|
||||
// Categories field
|
||||
TextFieldTags(
|
||||
textfieldTagsController: categoriesController,
|
||||
textSeparators: const [' ', ','],
|
||||
letterCase: LetterCase.small,
|
||||
validator: (String tag) {
|
||||
if (tag.isEmpty) return 'No, cannot be empty';
|
||||
if (tag.contains(' ')) return 'Tags should be URL-safe';
|
||||
return null;
|
||||
},
|
||||
inputFieldBuilder: (context, inputFieldValues) {
|
||||
return ChipTagInputField(
|
||||
inputFieldValues: inputFieldValues,
|
||||
labelText: 'categories',
|
||||
hintText: 'categoriesHint',
|
||||
);
|
||||
// FIXME: Sometimes the entire dropdown crashes: 'package:flutter/src/rendering/stack.dart': Failed assertion: line 799 pos 12: 'firstChild == null || child != null': is not true.
|
||||
DropdownButtonFormField2<SnPostCategory>(
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 9),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
hint: Text('categories'.tr(), style: TextStyle(fontSize: 15)),
|
||||
items:
|
||||
(postCategories.value ?? <SnPostCategory>[]).map((item) {
|
||||
return DropdownMenuItem(
|
||||
value: item,
|
||||
enabled: false,
|
||||
child: StatefulBuilder(
|
||||
builder: (context, menuSetState) {
|
||||
final isSelected = state.categories.value.contains(
|
||||
item,
|
||||
);
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
isSelected
|
||||
? state.categories.value =
|
||||
state.categories.value
|
||||
.where((e) => e != item)
|
||||
.toList()
|
||||
: state.categories.value = [
|
||||
...state.categories.value,
|
||||
item,
|
||||
];
|
||||
menuSetState(() {});
|
||||
},
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (isSelected)
|
||||
const Icon(Icons.check_box_outlined)
|
||||
else
|
||||
const Icon(Icons.check_box_outline_blank),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.categoryDisplayTitle,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
value: currentCategories.isEmpty ? null : currentCategories.last,
|
||||
onChanged: (_) {},
|
||||
selectedItemBuilder: (context) {
|
||||
return currentCategories.map((item) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
for (final category in currentCategories)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
child: Text(
|
||||
category.categoryDisplayTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.only(left: 16, right: 8),
|
||||
height: 40,
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 40,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
|
||||
// Visibility setting
|
||||
|
29
lib/widgets/post/compose_settings_sheet.g.dart
Normal file
29
lib/widgets/post/compose_settings_sheet.g.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'compose_settings_sheet.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$postCategoriesHash() => r'24337fe806d088b6468a350f62d5a5d40232a73c';
|
||||
|
||||
/// See also [postCategories].
|
||||
@ProviderFor(postCategories)
|
||||
final postCategoriesProvider =
|
||||
AutoDisposeFutureProvider<List<SnPostCategory>>.internal(
|
||||
postCategories,
|
||||
name: r'postCategoriesProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postCategoriesHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef PostCategoriesRef = AutoDisposeFutureProviderRef<List<SnPostCategory>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
@@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/models/post_category.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
@@ -30,8 +31,8 @@ class ComposeState {
|
||||
final ValueNotifier<Map<int, double>> attachmentProgress;
|
||||
final ValueNotifier<SnPublisher?> currentPublisher;
|
||||
final ValueNotifier<bool> submitting;
|
||||
final ValueNotifier<List<SnPostCategory>> categories;
|
||||
StringTagController tagsController;
|
||||
StringTagController categoriesController;
|
||||
final String draftId;
|
||||
int postType;
|
||||
// Linked poll id for this compose session (nullable)
|
||||
@@ -48,7 +49,7 @@ class ComposeState {
|
||||
required this.currentPublisher,
|
||||
required this.submitting,
|
||||
required this.tagsController,
|
||||
required this.categoriesController,
|
||||
required this.categories,
|
||||
required this.draftId,
|
||||
this.postType = 0,
|
||||
String? pollId,
|
||||
@@ -80,11 +81,7 @@ class ComposeLogic {
|
||||
}) {
|
||||
final id = draftId ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final tagsController = StringTagController();
|
||||
final categoriesController = StringTagController();
|
||||
originalPost?.tags.forEach((x) => tagsController.addTag(x.slug));
|
||||
originalPost?.categories.forEach(
|
||||
(x) => categoriesController.addTag(x.slug),
|
||||
);
|
||||
return ComposeState(
|
||||
attachments: ValueNotifier<List<UniversalFile>>(
|
||||
originalPost?.attachments
|
||||
@@ -112,7 +109,9 @@ class ComposeLogic {
|
||||
attachmentProgress: ValueNotifier<Map<int, double>>({}),
|
||||
currentPublisher: ValueNotifier<SnPublisher?>(originalPost?.publisher),
|
||||
tagsController: tagsController,
|
||||
categoriesController: categoriesController,
|
||||
categories: ValueNotifier<List<SnPostCategory>>(
|
||||
originalPost?.categories ?? [],
|
||||
),
|
||||
draftId: id,
|
||||
postType: postType,
|
||||
// initialize without poll by default
|
||||
@@ -141,7 +140,7 @@ class ComposeLogic {
|
||||
attachmentProgress: ValueNotifier<Map<int, double>>({}),
|
||||
currentPublisher: ValueNotifier<SnPublisher?>(null),
|
||||
tagsController: tagsController,
|
||||
categoriesController: categoriesController,
|
||||
categories: ValueNotifier<List<SnPostCategory>>([]),
|
||||
draftId: draft.id,
|
||||
postType: postType,
|
||||
pollId: null,
|
||||
@@ -640,7 +639,7 @@ class ComposeLogic {
|
||||
if (repliedPost != null) 'replied_post_id': repliedPost.id,
|
||||
if (forwardedPost != null) 'forwarded_post_id': forwardedPost.id,
|
||||
'tags': state.tagsController.getTags,
|
||||
'categories': state.categoriesController.getTags,
|
||||
'categories': state.categories.value.map((e) => e.slug).toList(),
|
||||
if (state.pollId.value != null) 'poll_id': state.pollId.value,
|
||||
};
|
||||
|
||||
@@ -689,7 +688,7 @@ class ComposeLogic {
|
||||
}
|
||||
|
||||
static void handleKeyPress(
|
||||
RawKeyEvent event,
|
||||
KeyEvent event,
|
||||
ComposeState state,
|
||||
WidgetRef ref,
|
||||
BuildContext context, {
|
||||
@@ -697,11 +696,13 @@ class ComposeLogic {
|
||||
SnPost? repliedPost,
|
||||
SnPost? forwardedPost,
|
||||
}) {
|
||||
if (event is! RawKeyDownEvent) return;
|
||||
if (event is! KeyDownEvent) return;
|
||||
|
||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||
final isSave = event.logicalKey == LogicalKeyboardKey.keyS;
|
||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
||||
final isModifierPressed =
|
||||
HardwareKeyboard.instance.isMetaPressed ||
|
||||
HardwareKeyboard.instance.isControlPressed;
|
||||
final isSubmit = event.logicalKey == LogicalKeyboardKey.enter;
|
||||
|
||||
if (isPaste && isModifierPressed) {
|
||||
@@ -731,7 +732,7 @@ class ComposeLogic {
|
||||
state.attachmentProgress.dispose();
|
||||
state.currentPublisher.dispose();
|
||||
state.tagsController.dispose();
|
||||
state.categoriesController.dispose();
|
||||
state.categories.dispose();
|
||||
state.pollId.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -63,6 +63,7 @@ class ComposeToolbar extends HookConsumerWidget {
|
||||
|
||||
return Material(
|
||||
elevation: 4,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
|
@@ -279,18 +279,14 @@ class _DraftItem extends StatelessWidget {
|
||||
|
||||
String _parseVisibility(int visibility) {
|
||||
switch (visibility) {
|
||||
case 0:
|
||||
return 'public'.tr();
|
||||
case 1:
|
||||
return 'unlisted'.tr();
|
||||
return 'postVisibilityFriends';
|
||||
case 2:
|
||||
return 'friends'.tr();
|
||||
return 'postVisibilityUnlisted';
|
||||
case 3:
|
||||
return 'selected'.tr();
|
||||
case 4:
|
||||
return 'private'.tr();
|
||||
return 'postVisibilityPrivate';
|
||||
default:
|
||||
return 'unknown'.tr();
|
||||
return 'postVisibilityPublic';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
105
lib/widgets/post/post_featured.dart
Normal file
105
lib/widgets/post/post_featured.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
part 'post_featured.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<SnPost>> featuredPosts(Ref ref) async {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
final resp = await apiClient.get('/sphere/posts/featured');
|
||||
return resp.data.map((e) => SnPost.fromJson(e)).cast<SnPost>().toList();
|
||||
}
|
||||
|
||||
class PostFeaturedList extends HookConsumerWidget {
|
||||
const PostFeaturedList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final featuredPostsAsync = ref.watch(featuredPostsProvider);
|
||||
|
||||
final pageViewController = usePageController();
|
||||
final pageViewCurrent = useState(0);
|
||||
|
||||
useEffect(() {
|
||||
pageViewController.addListener(() {
|
||||
pageViewCurrent.value = pageViewController.page?.round() ?? 0;
|
||||
});
|
||||
return null;
|
||||
}, [pageViewController]);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(Symbols.highlight),
|
||||
Text('Highlight Posts'),
|
||||
Spacer(),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: const BoxConstraints(),
|
||||
onPressed: () {
|
||||
pageViewController.animateToPage(
|
||||
pageViewCurrent.value - 1,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.arrow_left),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: const BoxConstraints(),
|
||||
onPressed: () {
|
||||
pageViewController.animateToPage(
|
||||
pageViewCurrent.value + 1,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.arrow_right),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 8),
|
||||
featuredPostsAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||
data: (posts) {
|
||||
return SizedBox(
|
||||
height: 320,
|
||||
child: PageView.builder(
|
||||
controller: pageViewController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: posts.length,
|
||||
itemBuilder: (context, index) {
|
||||
return SingleChildScrollView(
|
||||
child: PostActionableItem(
|
||||
item: posts[index],
|
||||
borderRadius: 8,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
28
lib/widgets/post/post_featured.g.dart
Normal file
28
lib/widgets/post/post_featured.g.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'post_featured.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$featuredPostsHash() => r'4b7fffb02eac72f5861b02af1b1e5da36b571698';
|
||||
|
||||
/// See also [featuredPosts].
|
||||
@ProviderFor(featuredPosts)
|
||||
final featuredPostsProvider = AutoDisposeFutureProvider<List<SnPost>>.internal(
|
||||
featuredPosts,
|
||||
name: r'featuredPostsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$featuredPostsHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef FeaturedPostsRef = AutoDisposeFutureProviderRef<List<SnPost>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
@@ -16,6 +16,7 @@ import 'package:island/pods/userinfo.dart';
|
||||
import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/services/responsive.dart';
|
||||
import 'package:island/services/time.dart';
|
||||
import 'package:island/utils/mapping.dart';
|
||||
import 'package:island/widgets/account/account_name.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||
@@ -272,7 +273,7 @@ class PostItem extends HookConsumerWidget {
|
||||
: item.reactionsCount.entries
|
||||
.sortedBy((e) => e.value)
|
||||
.map((e) => e.key)
|
||||
.first;
|
||||
.last;
|
||||
|
||||
final postLanguage =
|
||||
item.content != null
|
||||
@@ -314,6 +315,19 @@ class PostItem extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
String parseVisibility(int visibility) {
|
||||
switch (visibility) {
|
||||
case 1:
|
||||
return 'postVisibilityFriends';
|
||||
case 2:
|
||||
return 'postVisibilityUnlisted';
|
||||
case 3:
|
||||
return 'postVisibilityPrivate';
|
||||
default:
|
||||
return 'postVisibilityPublic';
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -349,13 +363,27 @@ class PostItem extends HookConsumerWidget {
|
||||
Text('@${item.publisher.name}').fontSize(11),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
isFullPost
|
||||
? (item.publishedAt ?? item.createdAt)!.formatSystem()
|
||||
: (item.publishedAt ?? item.createdAt)!.formatRelative(
|
||||
context,
|
||||
),
|
||||
).fontSize(10),
|
||||
Row(
|
||||
spacing: 6,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
isFullPost
|
||||
? (item.publishedAt ?? item.createdAt)!
|
||||
.formatSystem()
|
||||
: (item.publishedAt ?? item.createdAt)!
|
||||
.formatRelative(context),
|
||||
).fontSize(10),
|
||||
if (item.editedAt != null)
|
||||
Text(
|
||||
'editedAt'.tr(args: [item.editedAt!.formatSystem()]),
|
||||
).fontSize(10),
|
||||
if (item.visibility != 0)
|
||||
Text(
|
||||
parseVisibility(item.visibility).tr(),
|
||||
).fontSize(10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -452,7 +480,9 @@ class PostItem extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
)
|
||||
else if (item.content?.isNotEmpty ?? false)
|
||||
else if ((item.content?.isNotEmpty ?? false) ||
|
||||
(item.title?.isNotEmpty ?? false) ||
|
||||
(item.description?.isNotEmpty ?? false))
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: renderingPadding.horizontal,
|
||||
@@ -535,7 +565,7 @@ class PostItem extends HookConsumerWidget {
|
||||
right: renderingPadding.horizontal,
|
||||
),
|
||||
),
|
||||
if (item.attachments.isNotEmpty)
|
||||
if (item.attachments.isNotEmpty && item.type != 1)
|
||||
CloudFileList(
|
||||
files: item.attachments,
|
||||
padding: EdgeInsets.symmetric(
|
||||
@@ -543,37 +573,95 @@ class PostItem extends HookConsumerWidget {
|
||||
vertical: 4,
|
||||
),
|
||||
),
|
||||
if (item.tags.isNotEmpty)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 2,
|
||||
children: [
|
||||
if (item.tags.isNotEmpty)
|
||||
Wrap(
|
||||
runAlignment: WrapAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(Symbols.label, size: 16).padding(top: 2),
|
||||
for (final tag
|
||||
in isFullPost ? item.tags : item.tags.take(3))
|
||||
InkWell(
|
||||
child: Text('#${tag.name ?? tag.slug}'),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postTagDetail',
|
||||
pathParameters: {'slug': tag.slug},
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!isFullPost && item.tags.length > 3)
|
||||
Text('+${item.tags.length - 3}').opacity(0.6),
|
||||
],
|
||||
),
|
||||
if (item.categories.isNotEmpty)
|
||||
Wrap(
|
||||
runAlignment: WrapAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(Symbols.category, size: 16).padding(top: 2),
|
||||
for (final category
|
||||
in isFullPost
|
||||
? item.categories
|
||||
: item.categories.take(2))
|
||||
InkWell(
|
||||
child: Text(category.categoryDisplayTitle),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postCategoryDetail',
|
||||
pathParameters: {'slug': category.slug},
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!isFullPost && item.categories.length > 2)
|
||||
Text('+${item.categories.length - 2}').opacity(0.6),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(horizontal: renderingPadding.horizontal + 4, top: 4),
|
||||
if (item.meta?['embeds'] != null)
|
||||
...((item.meta!['embeds'] as List<dynamic>).map(
|
||||
(embedData) => switch (embedData['type']) {
|
||||
'link' => EmbedLinkWidget(
|
||||
link: SnEmbedLink.fromJson(embedData as Map<String, dynamic>),
|
||||
maxWidth: math.min(
|
||||
MediaQuery.of(context).size.width,
|
||||
kWideScreenWidth,
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: renderingPadding.horizontal,
|
||||
right: renderingPadding.horizontal,
|
||||
),
|
||||
),
|
||||
'poll' => Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: renderingPadding.horizontal,
|
||||
vertical: 8,
|
||||
),
|
||||
child: PollSubmit(
|
||||
initialAnswers: embedData['poll']?['user_answer']?['answer'],
|
||||
stats: embedData['poll']?['stats'],
|
||||
poll: SnPollWithStats.fromJson(embedData['poll']),
|
||||
onSubmit: (_) {},
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
),
|
||||
_ => Text('Unable show embed: ${embedData['type']}'),
|
||||
},
|
||||
)),
|
||||
...((item.meta!['embeds'] as List<dynamic>)
|
||||
.map((embedData) => convertMapKeysToSnakeCase(embedData))
|
||||
.map(
|
||||
(embedData) => switch (embedData['type']) {
|
||||
'link' => EmbedLinkWidget(
|
||||
link: SnScrappedLink.fromJson(embedData),
|
||||
maxWidth: math.min(
|
||||
MediaQuery.of(context).size.width,
|
||||
kWideScreenWidth,
|
||||
),
|
||||
margin: EdgeInsets.only(
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: renderingPadding.horizontal,
|
||||
right: renderingPadding.horizontal,
|
||||
),
|
||||
),
|
||||
'poll' => Card(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: renderingPadding.horizontal,
|
||||
vertical: 8,
|
||||
),
|
||||
child:
|
||||
embedData['poll'] == null
|
||||
? Text('Poll was not loaded...')
|
||||
: PollSubmit(
|
||||
initialAnswers:
|
||||
embedData['poll']?['user_answer']?['answer'],
|
||||
stats: embedData['poll']?['stats'],
|
||||
poll: SnPollWithStats.fromJson(embedData['poll']),
|
||||
onSubmit: (_) {},
|
||||
).padding(horizontal: 16, vertical: 12),
|
||||
),
|
||||
_ => Text('Unable show embed: ${embedData['type']}'),
|
||||
},
|
||||
)),
|
||||
if (isShowReference)
|
||||
_buildReferencePost(context, item, renderingPadding),
|
||||
if (item.repliesCount > 0 && isEmbedReply)
|
||||
@@ -770,7 +858,7 @@ class PostReplyPreview extends HookConsumerWidget {
|
||||
final posts = useState<List<SnPost>>([]);
|
||||
final loading = useState(false);
|
||||
|
||||
Future<void> fetchMoreReplies({int pageSize = 1}) async {
|
||||
Future<void> fetchMoreReplies({int pageSize = 3}) async {
|
||||
final client = ref.read(apiClientProvider);
|
||||
loading.value = true;
|
||||
|
||||
@@ -779,10 +867,14 @@ class PostReplyPreview extends HookConsumerWidget {
|
||||
'/sphere/posts/${parent.id}/replies',
|
||||
queryParameters: {'offset': posts.value.length, 'take': pageSize},
|
||||
);
|
||||
posts.value = [
|
||||
...posts.value,
|
||||
...response.data.map((e) => SnPost.fromJson(e)),
|
||||
];
|
||||
try {
|
||||
posts.value = [
|
||||
...posts.value,
|
||||
...response.data.map((e) => SnPost.fromJson(e)),
|
||||
];
|
||||
} catch (_) {
|
||||
// ignore disposed
|
||||
}
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
@@ -877,38 +969,40 @@ class PostReplyPreview extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
)
|
||||
: featuredReply!.when(
|
||||
: (featuredReply!).map(
|
||||
data:
|
||||
(value) => Row(
|
||||
(data) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
ProfilePictureWidget(
|
||||
file: value?.publisher.picture,
|
||||
file: data.value?.publisher.picture,
|
||||
radius: 12,
|
||||
).padding(top: 4),
|
||||
if (value?.content?.isNotEmpty ?? false)
|
||||
if (data.value?.content?.isNotEmpty ?? false)
|
||||
Expanded(
|
||||
child: MarkdownTextContent(content: value!.content!),
|
||||
child: MarkdownTextContent(
|
||||
content: data.value!.content!,
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Text(
|
||||
'postHasAttachments',
|
||||
).plural(value?.attachments.length ?? 0),
|
||||
).plural(data.value?.attachments.length ?? 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
error:
|
||||
(error, _) => Row(
|
||||
(e) => Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(Symbols.close, size: 18),
|
||||
Text(error.toString()),
|
||||
Text(e.error.toString()),
|
||||
],
|
||||
),
|
||||
loading:
|
||||
() => Row(
|
||||
(_) => Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
SizedBox(
|
||||
@@ -939,7 +1033,6 @@ class PostReplyPreview extends HookConsumerWidget {
|
||||
children: [
|
||||
Text('repliesCount')
|
||||
.plural(parent.repliesCount)
|
||||
.tr()
|
||||
.fontSize(15)
|
||||
.bold()
|
||||
.padding(horizontal: 5),
|
||||
|
@@ -15,7 +15,12 @@ class PostListNotifier extends _$PostListNotifier
|
||||
static const int _pageSize = 20;
|
||||
|
||||
@override
|
||||
Future<CursorPagingData<SnPost>> build(String? pubName) {
|
||||
Future<CursorPagingData<SnPost>> build(
|
||||
String? pubName, {
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
}) {
|
||||
return fetch(cursor: null);
|
||||
}
|
||||
|
||||
@@ -28,6 +33,9 @@ class PostListNotifier extends _$PostListNotifier
|
||||
'offset': offset,
|
||||
'take': _pageSize,
|
||||
if (pubName != null) 'pub': pubName,
|
||||
if (type != null) 'type': type,
|
||||
if (tags != null) 'tags': tags,
|
||||
if (categories != null) 'categories': categories,
|
||||
};
|
||||
|
||||
final response = await client.get(
|
||||
@@ -60,6 +68,9 @@ enum PostItemType {
|
||||
|
||||
class SliverPostList extends HookConsumerWidget {
|
||||
final String? pubName;
|
||||
final int? type;
|
||||
final List<String>? categories;
|
||||
final List<String>? tags;
|
||||
final PostItemType itemType;
|
||||
final Color? backgroundColor;
|
||||
final EdgeInsets? padding;
|
||||
@@ -70,6 +81,9 @@ class SliverPostList extends HookConsumerWidget {
|
||||
const SliverPostList({
|
||||
super.key,
|
||||
this.pubName,
|
||||
this.type,
|
||||
this.categories,
|
||||
this.tags,
|
||||
this.itemType = PostItemType.regular,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
@@ -81,9 +95,26 @@ class SliverPostList extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return PagingHelperSliverView(
|
||||
provider: postListNotifierProvider(pubName),
|
||||
futureRefreshable: postListNotifierProvider(pubName).future,
|
||||
notifierRefreshable: postListNotifierProvider(pubName).notifier,
|
||||
provider: postListNotifierProvider(
|
||||
pubName,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
),
|
||||
futureRefreshable:
|
||||
postListNotifierProvider(
|
||||
pubName,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
).future,
|
||||
notifierRefreshable:
|
||||
postListNotifierProvider(
|
||||
pubName,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
).notifier,
|
||||
contentBuilder:
|
||||
(data, widgetCount, endItemView) => SliverList.builder(
|
||||
itemCount: widgetCount,
|
||||
|
@@ -6,7 +6,7 @@ part of 'post_list.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$postListNotifierHash() => r'2e4fb36123d3f97ac1edf9945043251d4eb519a2';
|
||||
String _$postListNotifierHash() => r'2ca4f3cfbbcd04f3cc32e7f7bd511a5811042829';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
@@ -32,8 +32,16 @@ class _SystemHash {
|
||||
abstract class _$PostListNotifier
|
||||
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> {
|
||||
late final String? pubName;
|
||||
late final int? type;
|
||||
late final List<String>? categories;
|
||||
late final List<String>? tags;
|
||||
|
||||
FutureOr<CursorPagingData<SnPost>> build(String? pubName);
|
||||
FutureOr<CursorPagingData<SnPost>> build(
|
||||
String? pubName, {
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
});
|
||||
}
|
||||
|
||||
/// See also [PostListNotifier].
|
||||
@@ -47,15 +55,30 @@ class PostListNotifierFamily
|
||||
const PostListNotifierFamily();
|
||||
|
||||
/// See also [PostListNotifier].
|
||||
PostListNotifierProvider call(String? pubName) {
|
||||
return PostListNotifierProvider(pubName);
|
||||
PostListNotifierProvider call(
|
||||
String? pubName, {
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
}) {
|
||||
return PostListNotifierProvider(
|
||||
pubName,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
PostListNotifierProvider getProviderOverride(
|
||||
covariant PostListNotifierProvider provider,
|
||||
) {
|
||||
return call(provider.pubName);
|
||||
return call(
|
||||
provider.pubName,
|
||||
type: provider.type,
|
||||
categories: provider.categories,
|
||||
tags: provider.tags,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
@@ -81,20 +104,32 @@ class PostListNotifierProvider
|
||||
CursorPagingData<SnPost>
|
||||
> {
|
||||
/// See also [PostListNotifier].
|
||||
PostListNotifierProvider(String? pubName)
|
||||
: this._internal(
|
||||
() => PostListNotifier()..pubName = pubName,
|
||||
from: postListNotifierProvider,
|
||||
name: r'postListNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postListNotifierHash,
|
||||
dependencies: PostListNotifierFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
PostListNotifierFamily._allTransitiveDependencies,
|
||||
pubName: pubName,
|
||||
);
|
||||
PostListNotifierProvider(
|
||||
String? pubName, {
|
||||
int? type,
|
||||
List<String>? categories,
|
||||
List<String>? tags,
|
||||
}) : this._internal(
|
||||
() =>
|
||||
PostListNotifier()
|
||||
..pubName = pubName
|
||||
..type = type
|
||||
..categories = categories
|
||||
..tags = tags,
|
||||
from: postListNotifierProvider,
|
||||
name: r'postListNotifierProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$postListNotifierHash,
|
||||
dependencies: PostListNotifierFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
PostListNotifierFamily._allTransitiveDependencies,
|
||||
pubName: pubName,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
);
|
||||
|
||||
PostListNotifierProvider._internal(
|
||||
super._createNotifier, {
|
||||
@@ -104,15 +139,26 @@ class PostListNotifierProvider
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.pubName,
|
||||
required this.type,
|
||||
required this.categories,
|
||||
required this.tags,
|
||||
}) : super.internal();
|
||||
|
||||
final String? pubName;
|
||||
final int? type;
|
||||
final List<String>? categories;
|
||||
final List<String>? tags;
|
||||
|
||||
@override
|
||||
FutureOr<CursorPagingData<SnPost>> runNotifierBuild(
|
||||
covariant PostListNotifier notifier,
|
||||
) {
|
||||
return notifier.build(pubName);
|
||||
return notifier.build(
|
||||
pubName,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -120,13 +166,21 @@ class PostListNotifierProvider
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PostListNotifierProvider._internal(
|
||||
() => create()..pubName = pubName,
|
||||
() =>
|
||||
create()
|
||||
..pubName = pubName
|
||||
..type = type
|
||||
..categories = categories
|
||||
..tags = tags,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
pubName: pubName,
|
||||
type: type,
|
||||
categories: categories,
|
||||
tags: tags,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -142,13 +196,20 @@ class PostListNotifierProvider
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PostListNotifierProvider && other.pubName == pubName;
|
||||
return other is PostListNotifierProvider &&
|
||||
other.pubName == pubName &&
|
||||
other.type == type &&
|
||||
other.categories == categories &&
|
||||
other.tags == tags;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, pubName.hashCode);
|
||||
hash = _SystemHash.combine(hash, type.hashCode);
|
||||
hash = _SystemHash.combine(hash, categories.hashCode);
|
||||
hash = _SystemHash.combine(hash, tags.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
@@ -160,6 +221,15 @@ mixin PostListNotifierRef
|
||||
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> {
|
||||
/// The parameter `pubName` of this provider.
|
||||
String? get pubName;
|
||||
|
||||
/// The parameter `type` of this provider.
|
||||
int? get type;
|
||||
|
||||
/// The parameter `categories` of this provider.
|
||||
List<String>? get categories;
|
||||
|
||||
/// The parameter `tags` of this provider.
|
||||
List<String>? get tags;
|
||||
}
|
||||
|
||||
class _PostListNotifierProviderElement
|
||||
@@ -173,6 +243,13 @@ class _PostListNotifierProviderElement
|
||||
|
||||
@override
|
||||
String? get pubName => (origin as PostListNotifierProvider).pubName;
|
||||
@override
|
||||
int? get type => (origin as PostListNotifierProvider).type;
|
||||
@override
|
||||
List<String>? get categories =>
|
||||
(origin as PostListNotifierProvider).categories;
|
||||
@override
|
||||
List<String>? get tags => (origin as PostListNotifierProvider).tags;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/models/publisher.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/publishers.dart';
|
||||
import 'package:island/screens/posts/compose.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/post/publishers_modal.dart';
|
||||
@@ -14,8 +16,14 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class PostQuickReply extends HookConsumerWidget {
|
||||
final SnPost parent;
|
||||
final Function? onPosted;
|
||||
const PostQuickReply({super.key, required this.parent, this.onPosted});
|
||||
final VoidCallback? onPosted;
|
||||
final VoidCallback? onLaunch;
|
||||
const PostQuickReply({
|
||||
super.key,
|
||||
required this.parent,
|
||||
this.onPosted,
|
||||
this.onLaunch,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -48,7 +56,7 @@ class PostQuickReply extends HookConsumerWidget {
|
||||
'content': contentController.text,
|
||||
'replied_post_id': parent.id,
|
||||
},
|
||||
options: Options(headers: {'X-Pub': currentPublisher.value?.name}),
|
||||
queryParameters: {'pub': currentPublisher.value?.name},
|
||||
);
|
||||
contentController.clear();
|
||||
onPosted?.call();
|
||||
@@ -83,9 +91,10 @@ class PostQuickReply extends HookConsumerWidget {
|
||||
child: TextField(
|
||||
controller: contentController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Post your reply',
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'postReplyPlaceholder'.tr(),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
@@ -97,6 +106,26 @@ class PostQuickReply extends HookConsumerWidget {
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
onLaunch?.call();
|
||||
GoRouter.of(context)
|
||||
.pushNamed(
|
||||
'postCompose',
|
||||
extra: PostComposeInitialState(
|
||||
content: contentController.text,
|
||||
replyingTo: parent,
|
||||
),
|
||||
)
|
||||
.then((value) {
|
||||
if (value != null) onPosted?.call();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Symbols.launch, size: 20),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
@@ -110,6 +139,7 @@ class PostQuickReply extends HookConsumerWidget {
|
||||
: Icon(Symbols.send, size: 20),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: submitting.value ? null : performAction,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user