Compare commits
28 Commits
3.1.0+115
...
b39e2e2d64
| Author | SHA1 | Date | |
|---|---|---|---|
| b39e2e2d64 | |||
| 84b1d6a346 | |||
| 28335dd548 | |||
| 7253e2d3ef | |||
| 4d489425fa | |||
| 890a8a44cf | |||
| 8e3583f57a | |||
| d0ff14659f | |||
| 1f7caaeaac | |||
| 9f9f42071a | |||
| 6bd6e994cb | |||
| 02e68d76ee | |||
| d04b06089c | |||
| 9be6fea2e0 | |||
| 6b1214a06f | |||
| 4597373ac9 | |||
| 047c8d93aa | |||
| 715f95ca22 | |||
| ba709012d7 | |||
| fd186f8391 | |||
| 262d36cd2d | |||
| f320855348 | |||
| ed90152462 | |||
| 6e5c5f1690 | |||
| 7c92dee097 | |||
| e4bb031138 | |||
| 97226ae96b | |||
| d8cd33e79a |
@@ -147,6 +147,9 @@
|
|||||||
"addVideo": "Add video",
|
"addVideo": "Add video",
|
||||||
"addPhoto": "Add photo",
|
"addPhoto": "Add photo",
|
||||||
"addFile": "Add file",
|
"addFile": "Add file",
|
||||||
|
"linkAttachment": "Link Attachment",
|
||||||
|
"fileIdCannotBeEmpty": "File ID cannot be empty",
|
||||||
|
"failedToFetchFile": "Failed to fetch file: {}",
|
||||||
"createDirectMessage": "Send new DM",
|
"createDirectMessage": "Send new DM",
|
||||||
"gotoDirectMessage": "Go to DM",
|
"gotoDirectMessage": "Go to DM",
|
||||||
"react": "React",
|
"react": "React",
|
||||||
@@ -352,6 +355,8 @@
|
|||||||
"postTitle": "Title",
|
"postTitle": "Title",
|
||||||
"postDescription": "Description",
|
"postDescription": "Description",
|
||||||
"call": "Call",
|
"call": "Call",
|
||||||
|
"callLeave": "Leave",
|
||||||
|
"callEnd": "End this call",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"loginResetPasswordSent": "Password reset link sent, please check your email inbox.",
|
"loginResetPasswordSent": "Password reset link sent, please check your email inbox.",
|
||||||
"accountDeletion": "Delete Account",
|
"accountDeletion": "Delete Account",
|
||||||
@@ -622,8 +627,8 @@
|
|||||||
"chatJoin": "Join the Chat",
|
"chatJoin": "Join the Chat",
|
||||||
"realmJoin": "Join the Realm",
|
"realmJoin": "Join the Realm",
|
||||||
"realmJoinSuccess": "Successfully joined the realm.",
|
"realmJoinSuccess": "Successfully joined the realm.",
|
||||||
"discoverRealms": "Discover Realms",
|
"discoverRealms": "Discover realms",
|
||||||
"discoverPublishers": "Discover Publishers",
|
"discoverPublishers": "Discover publishers",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"publisherMembers": "Collaborators",
|
"publisherMembers": "Collaborators",
|
||||||
"developerHub": "Developer Hub",
|
"developerHub": "Developer Hub",
|
||||||
@@ -702,5 +707,26 @@
|
|||||||
"aboutDeviceName": "Device Name",
|
"aboutDeviceName": "Device Name",
|
||||||
"aboutDeviceIdentifier": "Device Identifier",
|
"aboutDeviceIdentifier": "Device Identifier",
|
||||||
"donate": "Donate",
|
"donate": "Donate",
|
||||||
"donateDescription": "Support us to continue developing the Solar Network and keep the server up and running."
|
"donateDescription": "Support us to continue developing the Solar Network and keep the server up and running.",
|
||||||
}
|
"fileId": "File ID",
|
||||||
|
"fileIdHint": "The file ID is the ID you get after upload the file via the Solar Network Drive.",
|
||||||
|
"translate": "Translate",
|
||||||
|
"translating": "Translating",
|
||||||
|
"translated": "Translated",
|
||||||
|
"reactionThumbUp": "Thumbs Up",
|
||||||
|
"reactionThumbDown": "Thumbs Down",
|
||||||
|
"reactionJustOkay": "Just Okay",
|
||||||
|
"reactionCry": "Cry",
|
||||||
|
"reactionConfuse": "Confused",
|
||||||
|
"reactionClap": "Clap",
|
||||||
|
"reactionLaugh": "Laugh",
|
||||||
|
"reactionAngry": "Angry",
|
||||||
|
"reactionParty": "Party",
|
||||||
|
"reactionPray": "Pray",
|
||||||
|
"reactionHeart": "Heart",
|
||||||
|
"selectMicrophone": "Select Microphone",
|
||||||
|
"selectCamera": "Select Camera",
|
||||||
|
"switchedTo": "Switched to {}",
|
||||||
|
"connecting": "Connecting",
|
||||||
|
"repliesLoadMore": "Load more replies"
|
||||||
|
}
|
||||||
|
|||||||
@@ -123,6 +123,10 @@
|
|||||||
"addVideo": "添加视频",
|
"addVideo": "添加视频",
|
||||||
"addPhoto": "添加照片",
|
"addPhoto": "添加照片",
|
||||||
"addFile": "添加文件",
|
"addFile": "添加文件",
|
||||||
|
"addAttachmentById": "通过 ID 添加附件",
|
||||||
|
"enterFileId": "输入文件 ID",
|
||||||
|
"fileIdCannotBeEmpty": "文件 ID 不能为空",
|
||||||
|
"failedToFetchFile": "获取文件失败: {}",
|
||||||
"createDirectMessage": "创建新私人消息",
|
"createDirectMessage": "创建新私人消息",
|
||||||
"gotoDirectMessage": "前往私信",
|
"gotoDirectMessage": "前往私信",
|
||||||
"react": "反应",
|
"react": "反应",
|
||||||
|
|||||||
@@ -123,6 +123,10 @@
|
|||||||
"addVideo": "新增影片",
|
"addVideo": "新增影片",
|
||||||
"addPhoto": "新增照片",
|
"addPhoto": "新增照片",
|
||||||
"addFile": "新增檔案",
|
"addFile": "新增檔案",
|
||||||
|
"addAttachmentById": "透過 ID 新增附件",
|
||||||
|
"enterFileId": "輸入檔案 ID",
|
||||||
|
"fileIdCannotBeEmpty": "檔案 ID 不能為空",
|
||||||
|
"failedToFetchFile": "無法取得檔案: {}",
|
||||||
"createDirectMessage": "建立新私人訊息",
|
"createDirectMessage": "建立新私人訊息",
|
||||||
"gotoDirectMessage": "Go to DM",
|
"gotoDirectMessage": "Go to DM",
|
||||||
"react": "反應",
|
"react": "反應",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
platform :ios, '13.0'
|
platform :ios, '15.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
@@ -40,33 +40,33 @@ PODS:
|
|||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- DKImagePickerController/PhotoGallery
|
- DKImagePickerController/PhotoGallery
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase/CoreOnly (11.15.0):
|
- Firebase/CoreOnly (12.0.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.0.0)
|
||||||
- Firebase/Messaging (11.15.0):
|
- Firebase/Messaging (12.0.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.15.0)
|
- FirebaseMessaging (~> 12.0.0)
|
||||||
- firebase_core (3.15.2):
|
- firebase_core (4.0.0):
|
||||||
- Firebase/CoreOnly (= 11.15.0)
|
- Firebase/CoreOnly (= 12.0.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.2.10):
|
- firebase_messaging (16.0.0):
|
||||||
- Firebase/Messaging (= 11.15.0)
|
- Firebase/Messaging (= 12.0.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseCore (11.15.0):
|
- FirebaseCore (12.0.0):
|
||||||
- FirebaseCoreInternal (~> 11.15.0)
|
- FirebaseCoreInternal (~> 12.0.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreInternal (11.15.0):
|
- FirebaseCoreInternal (12.0.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseInstallations (11.15.0):
|
- FirebaseInstallations (12.0.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.0.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (11.15.0):
|
- FirebaseMessaging (12.0.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.0.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 12.0.0)
|
||||||
- GoogleDataTransport (~> 10.0)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Reachability (~> 8.1)
|
- GoogleUtilities/Reachability (~> 8.1)
|
||||||
@@ -93,9 +93,9 @@ PODS:
|
|||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
- flutter_webrtc (0.14.0):
|
- flutter_webrtc (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WebRTC-SDK (= 125.6422.07)
|
- WebRTC-SDK (= 137.7151.02)
|
||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -131,10 +131,10 @@ PODS:
|
|||||||
- irondash_engine_context (0.0.1):
|
- irondash_engine_context (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Kingfisher (8.5.0)
|
- Kingfisher (8.5.0)
|
||||||
- livekit_client (2.4.9):
|
- livekit_client (2.5.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- WebRTC-SDK (= 125.6422.07)
|
- WebRTC-SDK (= 137.7151.02)
|
||||||
- local_auth_darwin (0.0.1):
|
- local_auth_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -191,6 +191,8 @@ PODS:
|
|||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.50.3):
|
- sqlite3/rtree (3.50.3):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
|
- sqlite3/session (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -200,6 +202,7 @@ PODS:
|
|||||||
- sqlite3/math
|
- sqlite3/math
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
|
- sqlite3/session
|
||||||
- super_native_extensions (0.0.1):
|
- super_native_extensions (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
@@ -209,7 +212,7 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WebRTC-SDK (125.6422.07)
|
- WebRTC-SDK (137.7151.02)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Alamofire
|
- Alamofire
|
||||||
@@ -361,13 +364,13 @@ SPEC CHECKSUMS:
|
|||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
||||||
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
|
firebase_core: 633e1851ffe1b9ab875f6467a4f574c79cef02e4
|
||||||
firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707
|
firebase_messaging: d17feef781edc84ebefe62624fb384358ad96361
|
||||||
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a
|
||||||
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55
|
||||||
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
|
||||||
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
|
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||||
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
||||||
@@ -376,14 +379,14 @@ SPEC CHECKSUMS:
|
|||||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||||
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||||
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
||||||
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
|
flutter_webrtc: 6f7da106613d52ade777d5b4875a43f48c28b457
|
||||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||||
Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c
|
Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c
|
||||||
livekit_client: 3f79d79233a5bd13d5b541732624ef959d7c538e
|
livekit_client: e3b79b99405428aac439b6b76a254cd9a11dbbfb
|
||||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||||
@@ -404,14 +407,14 @@ SPEC CHECKSUMS:
|
|||||||
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
||||||
sqlite3_flutter_libs: ce0522d143cee6ef5e16587acfce8f476316e005
|
sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
|
||||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
|
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
|
||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
WebRTC-SDK: dff00a3892bc570b6014e046297782084071657e
|
WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b
|
||||||
|
|
||||||
PODFILE CHECKSUM: f6df17c2a0cbd7af89692fd3877231eaea40230f
|
PODFILE CHECKSUM: c818292390b02fa379036ea099713a332bd7193f
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<int> updateMessage(ChatMessagesCompanion message) {
|
Future<int> updateMessage(ChatMessagesCompanion message) {
|
||||||
return (update(chatMessages)
|
return into(chatMessages).insert(message, mode: InsertMode.insertOrReplace);
|
||||||
..where((m) => m.id.equals(message.id.value))).write(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> updateMessageStatus(String id, MessageStatus status) {
|
Future<int> updateMessageStatus(String id, MessageStatus status) {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
|
|||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
|
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
import 'package:island/services/timezone.dart';
|
import 'package:island/services/timezone.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
@@ -30,6 +29,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
@@ -51,6 +51,7 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await langdetect.initLangDetect();
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
|||||||
@@ -162,8 +162,6 @@ sealed class CallParticipant with _$CallParticipant {
|
|||||||
required String identity,
|
required String identity,
|
||||||
required String name,
|
required String name,
|
||||||
required DateTime joinedAt,
|
required DateTime joinedAt,
|
||||||
required String? accountId,
|
|
||||||
required SnChatMember? profile,
|
|
||||||
}) = _CallParticipant;
|
}) = _CallParticipant;
|
||||||
|
|
||||||
factory CallParticipant.fromJson(Map<String, dynamic> json) =>
|
factory CallParticipant.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -2498,7 +2498,7 @@ as List<CallParticipant>,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$CallParticipant {
|
mixin _$CallParticipant {
|
||||||
|
|
||||||
String get identity; String get name; DateTime get joinedAt; String? get accountId; SnChatMember? get profile;
|
String get identity; String get name; DateTime get joinedAt;
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -2511,16 +2511,16 @@ $CallParticipantCopyWith<CallParticipant> get copyWith => _$CallParticipantCopyW
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile);
|
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)';
|
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2531,11 +2531,11 @@ abstract mixin class $CallParticipantCopyWith<$Res> {
|
|||||||
factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl;
|
factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile
|
String identity, String name, DateTime joinedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$SnChatMemberCopyWith<$Res>? get profile;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -2548,29 +2548,15 @@ class _$CallParticipantCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as DateTime,
|
||||||
as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnChatMember?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
/// Create a copy of CallParticipant
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnChatMemberCopyWith<$Res>? get profile {
|
|
||||||
if (_self.profile == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) {
|
|
||||||
return _then(_self.copyWith(profile: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2649,10 +2635,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CallParticipant() when $default != null:
|
case _CallParticipant() when $default != null:
|
||||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);case _:
|
return $default(_that.identity,_that.name,_that.joinedAt);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2670,10 +2656,10 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, DateTime joinedAt) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CallParticipant():
|
case _CallParticipant():
|
||||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);}
|
return $default(_that.identity,_that.name,_that.joinedAt);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -2687,10 +2673,10 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, DateTime joinedAt)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CallParticipant() when $default != null:
|
case _CallParticipant() when $default != null:
|
||||||
return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.profile);case _:
|
return $default(_that.identity,_that.name,_that.joinedAt);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2702,14 +2688,12 @@ return $default(_that.identity,_that.name,_that.joinedAt,_that.accountId,_that.p
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _CallParticipant implements CallParticipant {
|
class _CallParticipant implements CallParticipant {
|
||||||
const _CallParticipant({required this.identity, required this.name, required this.joinedAt, required this.accountId, required this.profile});
|
const _CallParticipant({required this.identity, required this.name, required this.joinedAt});
|
||||||
factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json);
|
factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json);
|
||||||
|
|
||||||
@override final String identity;
|
@override final String identity;
|
||||||
@override final String name;
|
@override final String name;
|
||||||
@override final DateTime joinedAt;
|
@override final DateTime joinedAt;
|
||||||
@override final String? accountId;
|
|
||||||
@override final SnChatMember? profile;
|
|
||||||
|
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -2724,16 +2708,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.profile, profile) || other.profile == profile));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt,accountId,profile);
|
int get hashCode => Object.hash(runtimeType,identity,name,joinedAt);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt, accountId: $accountId, profile: $profile)';
|
return 'CallParticipant(identity: $identity, name: $name, joinedAt: $joinedAt)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2744,11 +2728,11 @@ abstract mixin class _$CallParticipantCopyWith<$Res> implements $CallParticipant
|
|||||||
factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl;
|
factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String identity, String name, DateTime joinedAt, String? accountId, SnChatMember? profile
|
String identity, String name, DateTime joinedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@override $SnChatMemberCopyWith<$Res>? get profile;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -2761,30 +2745,16 @@ class __$CallParticipantCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of CallParticipant
|
/// Create a copy of CallParticipant
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,Object? accountId = freezed,Object? profile = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? joinedAt = null,}) {
|
||||||
return _then(_CallParticipant(
|
return _then(_CallParticipant(
|
||||||
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
as String,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
as DateTime,
|
||||||
as String?,profile: freezed == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnChatMember?,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a copy of CallParticipant
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnChatMemberCopyWith<$Res>? get profile {
|
|
||||||
if (_self.profile == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnChatMemberCopyWith<$Res>(_self.profile!, (value) {
|
|
||||||
return _then(_self.copyWith(profile: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -285,11 +285,6 @@ _CallParticipant _$CallParticipantFromJson(Map<String, dynamic> json) =>
|
|||||||
identity: json['identity'] as String,
|
identity: json['identity'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
joinedAt: DateTime.parse(json['joined_at'] as String),
|
joinedAt: DateTime.parse(json['joined_at'] as String),
|
||||||
accountId: json['account_id'] as String?,
|
|
||||||
profile:
|
|
||||||
json['profile'] == null
|
|
||||||
? null
|
|
||||||
: SnChatMember.fromJson(json['profile'] as Map<String, dynamic>),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
||||||
@@ -297,8 +292,6 @@ Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
|
|||||||
'identity': instance.identity,
|
'identity': instance.identity,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'joined_at': instance.joinedAt.toIso8601String(),
|
'joined_at': instance.joinedAt.toIso8601String(),
|
||||||
'account_id': instance.accountId,
|
|
||||||
'profile': instance.profile?.toJson(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) =>
|
_SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ sealed class SnPost with _$SnPost {
|
|||||||
@Default([]) List<SnCloudFile> attachments,
|
@Default([]) List<SnCloudFile> attachments,
|
||||||
required SnPublisher publisher,
|
required SnPublisher publisher,
|
||||||
@Default({}) Map<String, int> reactionsCount,
|
@Default({}) Map<String, int> reactionsCount,
|
||||||
|
@Default({}) Map<String, bool> reactionsMade,
|
||||||
@Default([]) List<dynamic> reactions,
|
@Default([]) List<dynamic> reactions,
|
||||||
@Default([]) List<PostTag> tags,
|
@Default([]) List<PostTag> tags,
|
||||||
@Default([]) List<PostCategory> categories,
|
@Default([]) List<PostCategory> categories,
|
||||||
@@ -77,6 +78,13 @@ sealed class SnSubscriptionStatus with _$SnSubscriptionStatus {
|
|||||||
sealed class ReactInfo with _$ReactInfo {
|
sealed class ReactInfo with _$ReactInfo {
|
||||||
const factory ReactInfo({required String icon, required int attitude}) =
|
const factory ReactInfo({required String icon, required int attitude}) =
|
||||||
_ReactInfo;
|
_ReactInfo;
|
||||||
|
|
||||||
|
static String getTranslationKey(String templateKey) {
|
||||||
|
final parts = templateKey.split('_');
|
||||||
|
final camelCase =
|
||||||
|
parts.map((p) => p[0].toUpperCase() + p.substring(1)).join();
|
||||||
|
return 'reaction$camelCase';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Map<String, ReactInfo> kReactionTemplates = {
|
const Map<String, ReactInfo> kReactionTemplates = {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPost {
|
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; 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<PostTag> get tags; List<PostCategory> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -28,16 +28,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactionsMade, reactionsMade)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactionsMade),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
|
|||||||
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$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, 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<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class _$SnPostCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -91,7 +91,8 @@ as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwa
|
|||||||
as SnPost?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable
|
as SnPost?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||||
as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable
|
as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, int>,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable
|
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<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<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<PostCategory>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -226,10 +227,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, 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, 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<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPost() when $default != null:
|
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.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.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 _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -247,10 +248,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, 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, 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<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPost():
|
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.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);}
|
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.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);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -264,10 +265,10 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, 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, 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<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPost() when $default != null:
|
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.reactions,_that.tags,_that.categories,_that.collections,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.isTruncated);case _:
|
return $default(_that.id,_that.title,_that.description,_that.language,_that.editedAt,_that.publishedAt,_that.visibility,_that.content,_that.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 _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -279,7 +280,7 @@ return $default(_that.id,_that.title,_that.description,_that.language,_that.edit
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPost implements SnPost {
|
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 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,_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<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;
|
||||||
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@@ -326,6 +327,13 @@ class _SnPost implements SnPost {
|
|||||||
return EqualUnmodifiableMapView(_reactionsCount);
|
return EqualUnmodifiableMapView(_reactionsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Map<String, bool> _reactionsMade;
|
||||||
|
@override@JsonKey() Map<String, bool> get reactionsMade {
|
||||||
|
if (_reactionsMade is EqualUnmodifiableMapView) return _reactionsMade;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_reactionsMade);
|
||||||
|
}
|
||||||
|
|
||||||
final List<dynamic> _reactions;
|
final List<dynamic> _reactions;
|
||||||
@override@JsonKey() List<dynamic> get reactions {
|
@override@JsonKey() List<dynamic> get reactions {
|
||||||
if (_reactions is EqualUnmodifiableListView) return _reactions;
|
if (_reactions is EqualUnmodifiableListView) return _reactions;
|
||||||
@@ -372,16 +380,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactionsMade, _reactionsMade)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactionsMade),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactionsMade: $reactionsMade, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -392,7 +400,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$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, 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<PostTag> tags, List<PostCategory> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -409,7 +417,7 @@ class __$SnPostCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = freezed,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactionsMade = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? isTruncated = null,}) {
|
||||||
return _then(_SnPost(
|
return _then(_SnPost(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -435,7 +443,8 @@ as String?,forwardedPost: freezed == forwardedPost ? _self.forwardedPost : forwa
|
|||||||
as SnPost?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable
|
as SnPost?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||||
as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable
|
as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : reactionsCount // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, int>,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable
|
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<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<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<PostCategory>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
|||||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||||
) ??
|
) ??
|
||||||
const {},
|
const {},
|
||||||
|
reactionsMade:
|
||||||
|
(json['reactions_made'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, e as bool),
|
||||||
|
) ??
|
||||||
|
const {},
|
||||||
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
||||||
tags:
|
tags:
|
||||||
(json['tags'] as List<dynamic>?)
|
(json['tags'] as List<dynamic>?)
|
||||||
@@ -106,6 +111,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
|||||||
'attachments': instance.attachments.map((e) => e.toJson()).toList(),
|
'attachments': instance.attachments.map((e) => e.toJson()).toList(),
|
||||||
'publisher': instance.publisher.toJson(),
|
'publisher': instance.publisher.toJson(),
|
||||||
'reactions_count': instance.reactionsCount,
|
'reactions_count': instance.reactionsCount,
|
||||||
|
'reactions_made': instance.reactionsMade,
|
||||||
'reactions': instance.reactions,
|
'reactions': instance.reactions,
|
||||||
'tags': instance.tags.map((e) => e.toJson()).toList(),
|
'tags': instance.tags.map((e) => e.toJson()).toList(),
|
||||||
'categories': instance.categories.map((e) => e.toJson()).toList(),
|
'categories': instance.categories.map((e) => e.toJson()).toList(),
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import 'package:island/pods/userinfo.dart';
|
import 'dart:async';
|
||||||
import 'package:island/screens/chat/chat.dart';
|
|
||||||
import 'package:island/widgets/chat/call_button.dart';
|
import 'package:island/widgets/chat/call_button.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
|
||||||
|
|
||||||
part 'call.g.dart';
|
part 'call.g.dart';
|
||||||
part 'call.freezed.dart';
|
part 'call.freezed.dart';
|
||||||
@@ -42,7 +39,8 @@ sealed class CallParticipantLive with _$CallParticipantLive {
|
|||||||
}) = _CallParticipantLive;
|
}) = _CallParticipantLive;
|
||||||
|
|
||||||
bool get isSpeaking => remoteParticipant.isSpeaking;
|
bool get isSpeaking => remoteParticipant.isSpeaking;
|
||||||
bool get isMuted => remoteParticipant.isMuted;
|
bool get isMuted =>
|
||||||
|
remoteParticipant.isMuted || !remoteParticipant.isMicrophoneEnabled();
|
||||||
bool get isScreenSharing => remoteParticipant.isScreenShareEnabled();
|
bool get isScreenSharing => remoteParticipant.isScreenShareEnabled();
|
||||||
bool get isScreenSharingWithAudio =>
|
bool get isScreenSharingWithAudio =>
|
||||||
remoteParticipant.isScreenShareAudioEnabled();
|
remoteParticipant.isScreenShareAudioEnabled();
|
||||||
@@ -57,7 +55,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
LocalParticipant? _localParticipant;
|
LocalParticipant? _localParticipant;
|
||||||
List<CallParticipantLive> _participants = [];
|
List<CallParticipantLive> _participants = [];
|
||||||
final Map<String, CallParticipant> _participantInfoByIdentity = {};
|
final Map<String, CallParticipant> _participantInfoByIdentity = {};
|
||||||
StreamSubscription? _wsSubscription;
|
|
||||||
EventsListener? _roomListener;
|
EventsListener? _roomListener;
|
||||||
|
|
||||||
List<CallParticipantLive> get participants =>
|
List<CallParticipantLive> get participants =>
|
||||||
@@ -71,7 +68,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
@override
|
@override
|
||||||
CallState build() {
|
CallState build() {
|
||||||
// Subscribe to websocket updates
|
// Subscribe to websocket updates
|
||||||
_subscribeToParticipantsUpdate();
|
|
||||||
return const CallState(
|
return const CallState(
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
isMicrophoneEnabled: true,
|
isMicrophoneEnabled: true,
|
||||||
@@ -80,27 +76,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _subscribeToParticipantsUpdate() {
|
|
||||||
// Only subscribe once
|
|
||||||
if (_wsSubscription != null) return;
|
|
||||||
final ws = ref.read(websocketProvider);
|
|
||||||
_wsSubscription = ws.dataStream.listen((packet) {
|
|
||||||
if (packet.type == 'call.participants.update' && packet.data != null) {
|
|
||||||
final participantsData = packet.data!["participants"];
|
|
||||||
if (participantsData is List) {
|
|
||||||
final parsed =
|
|
||||||
participantsData
|
|
||||||
.map(
|
|
||||||
(e) =>
|
|
||||||
CallParticipant.fromJson(Map<String, dynamic>.from(e)),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
_updateLiveParticipants(parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initRoomListeners() {
|
void _initRoomListeners() {
|
||||||
if (_room == null) return;
|
if (_room == null) return;
|
||||||
_roomListener?.dispose();
|
_roomListener?.dispose();
|
||||||
@@ -143,8 +118,6 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
identity: remote.identity,
|
identity: remote.identity,
|
||||||
name: remote.identity,
|
name: remote.identity,
|
||||||
joinedAt: DateTime.now(),
|
joinedAt: DateTime.now(),
|
||||||
accountId: null,
|
|
||||||
profile: null,
|
|
||||||
);
|
);
|
||||||
return CallParticipantLive(
|
return CallParticipantLive(
|
||||||
participant: match,
|
participant: match,
|
||||||
@@ -169,16 +142,12 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
if (idx != -1) return participants[idx];
|
if (idx != -1) return participants[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
final userInfo = ref.read(userInfoProvider);
|
|
||||||
final roomIdentity = ref.read(chatroomIdentityProvider(_roomId));
|
|
||||||
// Otherwise, use info from the identity map or fallback to minimal
|
// Otherwise, use info from the identity map or fallback to minimal
|
||||||
return _participantInfoByIdentity[_localParticipant!.identity] ??
|
return _participantInfoByIdentity[_localParticipant!.identity] ??
|
||||||
CallParticipant(
|
CallParticipant(
|
||||||
identity: _localParticipant!.identity,
|
identity: _localParticipant!.identity,
|
||||||
name: _localParticipant!.identity,
|
name: _localParticipant!.identity,
|
||||||
joinedAt: DateTime.now(),
|
joinedAt: DateTime.now(),
|
||||||
accountId: userInfo.value?.id,
|
|
||||||
profile: roomIdentity.value,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +174,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
remoteParticipant: _localParticipant!,
|
remoteParticipant: _localParticipant!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
// Add remote participants
|
// Add remote participants
|
||||||
_participants.addAll(
|
_participants.addAll(
|
||||||
@@ -264,7 +234,8 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
duration: Duration(
|
duration: Duration(
|
||||||
milliseconds:
|
milliseconds:
|
||||||
(DateTime.now().millisecondsSinceEpoch -
|
(DateTime.now().millisecondsSinceEpoch -
|
||||||
(ongoingCall?.createdAt.millisecondsSinceEpoch ?? 0)),
|
(ongoingCall?.createdAt.millisecondsSinceEpoch ??
|
||||||
|
DateTime.now().millisecondsSinceEpoch)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -318,6 +289,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
stopOnMute: autostop,
|
stopOnMute: autostop,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,6 +298,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
final target = !_localParticipant!.isCameraEnabled();
|
final target = !_localParticipant!.isCameraEnabled();
|
||||||
state = state.copyWith(isCameraEnabled: target);
|
state = state.copyWith(isCameraEnabled: target);
|
||||||
await _localParticipant!.setCameraEnabled(target);
|
await _localParticipant!.setCameraEnabled(target);
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +307,7 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
final target = !_localParticipant!.isScreenShareEnabled();
|
final target = !_localParticipant!.isScreenShareEnabled();
|
||||||
state = state.copyWith(isScreenSharing: target);
|
state = state.copyWith(isScreenSharing: target);
|
||||||
await _localParticipant!.setScreenShareEnabled(target);
|
await _localParticipant!.setScreenShareEnabled(target);
|
||||||
|
state = state.copyWith();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +324,13 @@ class CallNotifier extends _$CallNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_wsSubscription?.cancel();
|
state = state.copyWith(
|
||||||
|
error: null,
|
||||||
|
isConnected: false,
|
||||||
|
isMicrophoneEnabled: false,
|
||||||
|
isCameraEnabled: false,
|
||||||
|
isScreenSharing: false,
|
||||||
|
);
|
||||||
_roomListener?.dispose();
|
_roomListener?.dispose();
|
||||||
_room?.removeListener(_onRoomChange);
|
_room?.removeListener(_onRoomChange);
|
||||||
_room?.dispose();
|
_room?.dispose();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'call.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$callNotifierHash() => r'107174cd6cfab6bfafe44f8c4a72a67bcb93217b';
|
String _$callNotifierHash() => r'e4312feadb5b34f186b5349a7ee8b671b842dafc';
|
||||||
|
|
||||||
/// See also [CallNotifier].
|
/// See also [CallNotifier].
|
||||||
@ProviderFor(CallNotifier)
|
@ProviderFor(CallNotifier)
|
||||||
|
|||||||
38
lib/pods/translate.dart
Normal file
38
lib/pods/translate.dart
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect;
|
||||||
|
|
||||||
|
part 'translate.freezed.dart';
|
||||||
|
part 'translate.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class TranslateQuery with _$TranslateQuery {
|
||||||
|
const factory TranslateQuery({required String text, required String lang}) =
|
||||||
|
_TranslateQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<String> translateString(Ref ref, TranslateQuery query) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
final response = await client.post(
|
||||||
|
'/sphere/translate',
|
||||||
|
queryParameters: {'to': query.lang},
|
||||||
|
data: jsonEncode(query.text),
|
||||||
|
);
|
||||||
|
return response.data as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
String? detectStringLanguage(Ref ref, String text) {
|
||||||
|
try {
|
||||||
|
return langdetect.detectLangs(text).firstOrNull?.lang;
|
||||||
|
} catch (err) {
|
||||||
|
log('[Language] Unable to detect text\'s language: $text');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
268
lib/pods/translate.freezed.dart
Normal file
268
lib/pods/translate.freezed.dart
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'translate.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$TranslateQuery {
|
||||||
|
|
||||||
|
String get text; String get lang;
|
||||||
|
/// Create a copy of TranslateQuery
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$TranslateQueryCopyWith<TranslateQuery> get copyWith => _$TranslateQueryCopyWithImpl<TranslateQuery>(this as TranslateQuery, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is TranslateQuery&&(identical(other.text, text) || other.text == text)&&(identical(other.lang, lang) || other.lang == lang));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,text,lang);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TranslateQuery(text: $text, lang: $lang)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $TranslateQueryCopyWith<$Res> {
|
||||||
|
factory $TranslateQueryCopyWith(TranslateQuery value, $Res Function(TranslateQuery) _then) = _$TranslateQueryCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String text, String lang
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$TranslateQueryCopyWithImpl<$Res>
|
||||||
|
implements $TranslateQueryCopyWith<$Res> {
|
||||||
|
_$TranslateQueryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final TranslateQuery _self;
|
||||||
|
final $Res Function(TranslateQuery) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TranslateQuery
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? lang = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,lang: null == lang ? _self.lang : lang // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [TranslateQuery].
|
||||||
|
extension TranslateQueryPatterns on TranslateQuery {
|
||||||
|
/// 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( _TranslateQuery value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TranslateQuery() 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( _TranslateQuery value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TranslateQuery():
|
||||||
|
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( _TranslateQuery value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TranslateQuery() 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 text, String lang)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TranslateQuery() when $default != null:
|
||||||
|
return $default(_that.text,_that.lang);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 text, String lang) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TranslateQuery():
|
||||||
|
return $default(_that.text,_that.lang);}
|
||||||
|
}
|
||||||
|
/// 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 text, String lang)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _TranslateQuery() when $default != null:
|
||||||
|
return $default(_that.text,_that.lang);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class _TranslateQuery implements TranslateQuery {
|
||||||
|
const _TranslateQuery({required this.text, required this.lang});
|
||||||
|
|
||||||
|
|
||||||
|
@override final String text;
|
||||||
|
@override final String lang;
|
||||||
|
|
||||||
|
/// Create a copy of TranslateQuery
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$TranslateQueryCopyWith<_TranslateQuery> get copyWith => __$TranslateQueryCopyWithImpl<_TranslateQuery>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TranslateQuery&&(identical(other.text, text) || other.text == text)&&(identical(other.lang, lang) || other.lang == lang));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,text,lang);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'TranslateQuery(text: $text, lang: $lang)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$TranslateQueryCopyWith<$Res> implements $TranslateQueryCopyWith<$Res> {
|
||||||
|
factory _$TranslateQueryCopyWith(_TranslateQuery value, $Res Function(_TranslateQuery) _then) = __$TranslateQueryCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String text, String lang
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$TranslateQueryCopyWithImpl<$Res>
|
||||||
|
implements _$TranslateQueryCopyWith<$Res> {
|
||||||
|
__$TranslateQueryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _TranslateQuery _self;
|
||||||
|
final $Res Function(_TranslateQuery) _then;
|
||||||
|
|
||||||
|
/// Create a copy of TranslateQuery
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? lang = null,}) {
|
||||||
|
return _then(_TranslateQuery(
|
||||||
|
text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,lang: null == lang ? _self.lang : lang // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
274
lib/pods/translate.g.dart
Normal file
274
lib/pods/translate.g.dart
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'translate.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$translateStringHash() => r'51d638cf07cbf3ffa9469298f5bd9c667bc0ccb7';
|
||||||
|
|
||||||
|
/// 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 [translateString].
|
||||||
|
@ProviderFor(translateString)
|
||||||
|
const translateStringProvider = TranslateStringFamily();
|
||||||
|
|
||||||
|
/// See also [translateString].
|
||||||
|
class TranslateStringFamily extends Family<AsyncValue<String>> {
|
||||||
|
/// See also [translateString].
|
||||||
|
const TranslateStringFamily();
|
||||||
|
|
||||||
|
/// See also [translateString].
|
||||||
|
TranslateStringProvider call(TranslateQuery query) {
|
||||||
|
return TranslateStringProvider(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TranslateStringProvider getProviderOverride(
|
||||||
|
covariant TranslateStringProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.query);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'translateStringProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [translateString].
|
||||||
|
class TranslateStringProvider extends AutoDisposeFutureProvider<String> {
|
||||||
|
/// See also [translateString].
|
||||||
|
TranslateStringProvider(TranslateQuery query)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => translateString(ref as TranslateStringRef, query),
|
||||||
|
from: translateStringProvider,
|
||||||
|
name: r'translateStringProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$translateStringHash,
|
||||||
|
dependencies: TranslateStringFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
TranslateStringFamily._allTransitiveDependencies,
|
||||||
|
query: query,
|
||||||
|
);
|
||||||
|
|
||||||
|
TranslateStringProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.query,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final TranslateQuery query;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<String> Function(TranslateStringRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: TranslateStringProvider._internal(
|
||||||
|
(ref) => create(ref as TranslateStringRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
query: query,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<String> createElement() {
|
||||||
|
return _TranslateStringProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is TranslateStringProvider && other.query == query;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, query.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin TranslateStringRef on AutoDisposeFutureProviderRef<String> {
|
||||||
|
/// The parameter `query` of this provider.
|
||||||
|
TranslateQuery get query;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TranslateStringProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<String>
|
||||||
|
with TranslateStringRef {
|
||||||
|
_TranslateStringProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TranslateQuery get query => (origin as TranslateStringProvider).query;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$detectStringLanguageHash() =>
|
||||||
|
r'697b68464b3d00927cc43ccc1ba8ba93f2a470ed';
|
||||||
|
|
||||||
|
/// See also [detectStringLanguage].
|
||||||
|
@ProviderFor(detectStringLanguage)
|
||||||
|
const detectStringLanguageProvider = DetectStringLanguageFamily();
|
||||||
|
|
||||||
|
/// See also [detectStringLanguage].
|
||||||
|
class DetectStringLanguageFamily extends Family<String?> {
|
||||||
|
/// See also [detectStringLanguage].
|
||||||
|
const DetectStringLanguageFamily();
|
||||||
|
|
||||||
|
/// See also [detectStringLanguage].
|
||||||
|
DetectStringLanguageProvider call(String text) {
|
||||||
|
return DetectStringLanguageProvider(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DetectStringLanguageProvider getProviderOverride(
|
||||||
|
covariant DetectStringLanguageProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'detectStringLanguageProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [detectStringLanguage].
|
||||||
|
class DetectStringLanguageProvider extends AutoDisposeProvider<String?> {
|
||||||
|
/// See also [detectStringLanguage].
|
||||||
|
DetectStringLanguageProvider(String text)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => detectStringLanguage(ref as DetectStringLanguageRef, text),
|
||||||
|
from: detectStringLanguageProvider,
|
||||||
|
name: r'detectStringLanguageProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$detectStringLanguageHash,
|
||||||
|
dependencies: DetectStringLanguageFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
DetectStringLanguageFamily._allTransitiveDependencies,
|
||||||
|
text: text,
|
||||||
|
);
|
||||||
|
|
||||||
|
DetectStringLanguageProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.text,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
String? Function(DetectStringLanguageRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: DetectStringLanguageProvider._internal(
|
||||||
|
(ref) => create(ref as DetectStringLanguageRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
text: text,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeProviderElement<String?> createElement() {
|
||||||
|
return _DetectStringLanguageProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is DetectStringLanguageProvider && other.text == text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, text.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin DetectStringLanguageRef on AutoDisposeProviderRef<String?> {
|
||||||
|
/// The parameter `text` of this provider.
|
||||||
|
String get text;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DetectStringLanguageProviderElement
|
||||||
|
extends AutoDisposeProviderElement<String?>
|
||||||
|
with DetectStringLanguageRef {
|
||||||
|
_DetectStringLanguageProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get text => (origin as DetectStringLanguageProvider).text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
@@ -46,6 +46,10 @@ class WebSocketService {
|
|||||||
final StreamController<WebSocketState> _statusStreamController =
|
final StreamController<WebSocketState> _statusStreamController =
|
||||||
StreamController<WebSocketState>.broadcast();
|
StreamController<WebSocketState>.broadcast();
|
||||||
Timer? _reconnectTimer;
|
Timer? _reconnectTimer;
|
||||||
|
Timer? _heartbeatTimer;
|
||||||
|
|
||||||
|
DateTime? _heartbeatAt;
|
||||||
|
Duration? _heartbeatDelay;
|
||||||
|
|
||||||
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
||||||
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
|
||||||
@@ -71,6 +75,7 @@ class WebSocketService {
|
|||||||
}
|
}
|
||||||
await _channel!.ready;
|
await _channel!.ready;
|
||||||
_statusStreamController.sink.add(WebSocketState.connected());
|
_statusStreamController.sink.add(WebSocketState.connected());
|
||||||
|
_scheduleHeartbeat();
|
||||||
_channel!.stream.listen(
|
_channel!.stream.listen(
|
||||||
(data) {
|
(data) {
|
||||||
final dataStr =
|
final dataStr =
|
||||||
@@ -80,6 +85,13 @@ class WebSocketService {
|
|||||||
log(
|
log(
|
||||||
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
|
"[WebSocket] Received packet: ${packet.type} ${packet.errorMessage}",
|
||||||
);
|
);
|
||||||
|
if (packet.type == 'pong' && _heartbeatAt != null) {
|
||||||
|
var now = DateTime.now();
|
||||||
|
_heartbeatDelay = now.difference(_heartbeatAt!);
|
||||||
|
log(
|
||||||
|
"[WebSocket] Server respond last heartbeat for ${_heartbeatDelay!.inMilliseconds} ms",
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
log('[WebSocket] Connection closed, attempting to reconnect...');
|
log('[WebSocket] Connection closed, attempting to reconnect...');
|
||||||
@@ -108,6 +120,19 @@ class WebSocketService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _scheduleHeartbeat() {
|
||||||
|
_heartbeatTimer?.cancel();
|
||||||
|
_heartbeatTimer = Timer.periodic(const Duration(seconds: 60), (_) {
|
||||||
|
_beatTheHeart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _beatTheHeart() {
|
||||||
|
_heartbeatAt = DateTime.now();
|
||||||
|
log('[WebSocket] We\'re beating the heart! $_heartbeatAt');
|
||||||
|
sendMessage(jsonEncode(WebSocketPacket(type: 'ping', data: null)));
|
||||||
|
}
|
||||||
|
|
||||||
WebSocketChannel? get ws => _channel;
|
WebSocketChannel? get ws => _channel;
|
||||||
|
|
||||||
void sendMessage(String message) {
|
void sendMessage(String message) {
|
||||||
|
|||||||
@@ -287,12 +287,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
builder: (context, state) => const AboutScreen(),
|
builder: (context, state) => const AboutScreen(),
|
||||||
),
|
),
|
||||||
|
|
||||||
GoRoute(
|
|
||||||
name: 'reportList',
|
|
||||||
path: '/safety/reports/me',
|
|
||||||
builder: (context, state) => const AbuseReportListScreen(),
|
|
||||||
),
|
|
||||||
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'reportDetail',
|
name: 'reportDetail',
|
||||||
path: '/safety/reports/me/:id',
|
path: '/safety/reports/me/:id',
|
||||||
@@ -439,14 +433,6 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
path: '/account/relationships',
|
path: '/account/relationships',
|
||||||
builder: (context, state) => const RelationshipScreen(),
|
builder: (context, state) => const RelationshipScreen(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
name: 'accountProfile',
|
|
||||||
path: '/account/:name',
|
|
||||||
builder: (context, state) {
|
|
||||||
final name = state.pathParameters['name']!;
|
|
||||||
return AccountProfileScreen(name: name);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'profileUpdate',
|
name: 'profileUpdate',
|
||||||
path: '/account/me/update',
|
path: '/account/me/update',
|
||||||
@@ -462,8 +448,22 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
path: '/account/me/settings',
|
path: '/account/me/settings',
|
||||||
builder: (context, state) => const AccountSettingsScreen(),
|
builder: (context, state) => const AccountSettingsScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: 'reportList',
|
||||||
|
path: '/safety/reports/me',
|
||||||
|
builder: (context, state) => const AbuseReportListScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
GoRoute(
|
||||||
|
name: 'accountProfile',
|
||||||
|
path: '/account/:name',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return AccountProfileScreen(name: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
|
isNoBackground: false,
|
||||||
appBar: AppBar(title: Text('about'.tr()), elevation: 0),
|
appBar: AppBar(title: Text('about'.tr()), elevation: 0),
|
||||||
body:
|
body:
|
||||||
_isLoading
|
_isLoading
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: isWide,
|
isNoBackground: isWide,
|
||||||
appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0),
|
appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
padding: getTabbedPadding(context),
|
padding: getTabbedPadding(context),
|
||||||
@@ -231,7 +231,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
title: Text('abuseReports').tr(),
|
title: Text('abuseReports').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.gavel),
|
leading: const Icon(Symbols.gavel),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () => context.pushNamed('reportList'),
|
onTap: () => context.pushNamed('reportList'),
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class EventCalanderScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: const PageBackButton(),
|
leading: const PageBackButton(),
|
||||||
title: Text('eventCalander').tr(),
|
title: Text('eventCalander').tr(),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'leveling.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$accountStellarSubscriptionHash() =>
|
String _$accountStellarSubscriptionHash() =>
|
||||||
r'37fb821460e3ac50b5cf777c933b6779f732daee';
|
r'80abcdefb3868775fd8fe3c980215713efff5948';
|
||||||
|
|
||||||
/// See also [accountStellarSubscription].
|
/// See also [accountStellarSubscription].
|
||||||
@ProviderFor(accountStellarSubscription)
|
@ProviderFor(accountStellarSubscription)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:island/pods/event_calendar.dart';
|
|||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/services/color.dart';
|
import 'package:island/services/color.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/services/time.dart';
|
import 'package:island/services/time.dart';
|
||||||
import 'package:island/services/timezone/native.dart';
|
import 'package:island/services/timezone/native.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
@@ -248,294 +249,367 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
|
Widget accountBasicInfo(SnAccount data) => Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ProfilePictureWidget(file: data.profile.picture, radius: 32),
|
||||||
|
const Gap(20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
AccountName(account: data, style: TextStyle(fontSize: 20)),
|
||||||
|
const Gap(6),
|
||||||
|
Text('@${data.name}').fontSize(14).opacity(0.85),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AccountStatusWidget(uname: name, padding: EdgeInsets.zero),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget accountProfileDetail(SnAccount data) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 24,
|
||||||
|
children: [
|
||||||
|
if (buildSubcolumn(data).isNotEmpty)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 2,
|
||||||
|
children: buildSubcolumn(data),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('bio').tr().bold(),
|
||||||
|
Text(
|
||||||
|
data.profile.bio.isEmpty
|
||||||
|
? 'descriptionNone'.tr()
|
||||||
|
: data.profile.bio,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.profile.timeZone.isNotEmpty)
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('timeZone').tr().bold(),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
|
textBaseline: TextBaseline.alphabetic,
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Text(data.profile.timeZone),
|
||||||
|
Text(
|
||||||
|
getTzInfo(
|
||||||
|
data.profile.timeZone,
|
||||||
|
).$2.formatCustomGlobal('HH:mm'),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(),
|
||||||
|
).fontSize(11),
|
||||||
|
Text(
|
||||||
|
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
||||||
|
).fontSize(11).opacity(0.75),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24);
|
||||||
|
|
||||||
|
Widget accountAction(SnAccount data) => Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
if (accountRelationship.value == null ||
|
||||||
|
accountRelationship.value!.status > -100)
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
foregroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: relationshipAction,
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? 'addFriendShort'
|
||||||
|
: 'added',
|
||||||
|
).tr(),
|
||||||
|
icon:
|
||||||
|
accountRelationship.value == null
|
||||||
|
? const Icon(Symbols.person_add)
|
||||||
|
: const Icon(Symbols.person_check),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (accountRelationship.value == null ||
|
||||||
|
accountRelationship.value!.status <= -100)
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
foregroundColor: WidgetStatePropertyAll(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: blockAction,
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
accountRelationship.value == null
|
||||||
|
? 'blockUser'
|
||||||
|
: 'unblockUser',
|
||||||
|
).tr(),
|
||||||
|
icon:
|
||||||
|
accountRelationship.value == null
|
||||||
|
? const Icon(Symbols.block)
|
||||||
|
: const Icon(Symbols.person_cancel),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
onPressed: directMessageAction,
|
||||||
|
icon: const Icon(Symbols.message),
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
accountChat.value == null
|
||||||
|
? 'createDirectMessage'
|
||||||
|
: 'gotoDirectMessage',
|
||||||
|
maxLines: 1,
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton.filled(
|
||||||
|
onPressed: () {
|
||||||
|
showAbuseReportSheet(
|
||||||
|
context,
|
||||||
|
resourceIdentifier: 'account/${data.id}',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.flag,
|
||||||
|
color: Theme.of(context).colorScheme.onError,
|
||||||
|
),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, vertical: 8),
|
||||||
|
);
|
||||||
|
|
||||||
return account.when(
|
return account.when(
|
||||||
data:
|
data:
|
||||||
(data) => AppScaffold(
|
(data) => AppScaffold(
|
||||||
body: CustomScrollView(
|
isNoBackground: false,
|
||||||
slivers: [
|
appBar:
|
||||||
SliverAppBar(
|
isWideScreen(context)
|
||||||
foregroundColor: appbarColor.value,
|
? AppBar(
|
||||||
expandedHeight: 180,
|
foregroundColor: appbarColor.value,
|
||||||
pinned: true,
|
leading: PageBackButton(
|
||||||
leading: PageBackButton(
|
color: appbarColor.value,
|
||||||
color: appbarColor.value,
|
shadows: [appbarShadow],
|
||||||
shadows: [appbarShadow],
|
|
||||||
),
|
|
||||||
flexibleSpace: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child:
|
|
||||||
data.profile.background?.id != null
|
|
||||||
? CloudImageWidget(
|
|
||||||
file: data.profile.background,
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).appBarTheme.backgroundColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
FlexibleSpaceBar(
|
flexibleSpace: Stack(
|
||||||
title: Text(
|
children: [
|
||||||
data.nick,
|
Positioned.fill(
|
||||||
style: TextStyle(
|
child:
|
||||||
color:
|
data.profile.background?.id != null
|
||||||
appbarColor.value ??
|
? CloudImageWidget(
|
||||||
Theme.of(context).appBarTheme.foregroundColor,
|
file: data.profile.background,
|
||||||
shadows: [appbarShadow],
|
)
|
||||||
|
: Container(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).appBarTheme.backgroundColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FlexibleSpaceBar(
|
||||||
|
title: Text(
|
||||||
|
data.nick,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
appbarColor.value ??
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).appBarTheme.foregroundColor,
|
||||||
|
shadows: [appbarShadow],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
body:
|
||||||
|
isWideScreen(context)
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||||
|
if (data.badges.isNotEmpty)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: BadgeList(
|
||||||
|
badges: data.badges,
|
||||||
|
).padding(horizontal: 24, bottom: 24),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
LevelingProgressCard(
|
||||||
|
level: data.profile.level,
|
||||||
|
experience: data.profile.experience,
|
||||||
|
progress: data.profile.levelingProgress,
|
||||||
|
),
|
||||||
|
if (data.profile.verification != null)
|
||||||
|
VerificationStatusCard(
|
||||||
|
mark: data.profile.verification!,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Flexible(
|
||||||
],
|
child: CustomScrollView(
|
||||||
),
|
slivers: [
|
||||||
),
|
SliverToBoxAdapter(
|
||||||
SliverToBoxAdapter(
|
child: accountProfileDetail(data),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ProfilePictureWidget(
|
|
||||||
file: data.profile.picture,
|
|
||||||
radius: 32,
|
|
||||||
),
|
|
||||||
const Gap(20),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
AccountName(
|
|
||||||
account: data,
|
|
||||||
style: TextStyle(fontSize: 20),
|
|
||||||
),
|
|
||||||
const Gap(6),
|
|
||||||
Text(
|
|
||||||
'@${data.name}',
|
|
||||||
).fontSize(14).opacity(0.85),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
AccountStatusWidget(
|
|
||||||
uname: name,
|
if (user.value != null)
|
||||||
padding: EdgeInsets.zero,
|
SliverToBoxAdapter(child: accountAction(data)),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Card(
|
||||||
|
child: FortuneGraphWidget(
|
||||||
|
events: accountEvents,
|
||||||
|
eventCalanderUser: data.name,
|
||||||
|
),
|
||||||
|
).padding(all: 8),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
),
|
: CustomScrollView(
|
||||||
),
|
slivers: [
|
||||||
if (data.badges.isNotEmpty)
|
SliverAppBar(
|
||||||
SliverToBoxAdapter(
|
foregroundColor: appbarColor.value,
|
||||||
child: BadgeList(
|
expandedHeight: 180,
|
||||||
badges: data.badges,
|
pinned: true,
|
||||||
).padding(horizontal: 24, bottom: 24),
|
leading: PageBackButton(
|
||||||
),
|
color: appbarColor.value,
|
||||||
SliverToBoxAdapter(
|
shadows: [appbarShadow],
|
||||||
child: Column(
|
|
||||||
spacing: 12,
|
|
||||||
children: [
|
|
||||||
LevelingProgressCard(
|
|
||||||
level: data.profile.level,
|
|
||||||
experience: data.profile.experience,
|
|
||||||
progress: data.profile.levelingProgress,
|
|
||||||
),
|
|
||||||
if (data.profile.verification != null)
|
|
||||||
VerificationStatusCard(
|
|
||||||
mark: data.profile.verification!,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 20),
|
|
||||||
),
|
|
||||||
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: const Divider(height: 1).padding(vertical: 24),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
spacing: 24,
|
|
||||||
children: [
|
|
||||||
if (buildSubcolumn(data).isNotEmpty)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
spacing: 2,
|
|
||||||
children: buildSubcolumn(data),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('bio').tr().bold(),
|
|
||||||
Text(
|
|
||||||
data.profile.bio.isEmpty
|
|
||||||
? 'descriptionNone'.tr()
|
|
||||||
: data.profile.bio,
|
|
||||||
),
|
),
|
||||||
],
|
flexibleSpace: Stack(
|
||||||
),
|
children: [
|
||||||
if (data.profile.timeZone.isNotEmpty)
|
Positioned.fill(
|
||||||
Column(
|
child:
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
data.profile.background?.id != null
|
||||||
children: [
|
? CloudImageWidget(
|
||||||
Text('timeZone').tr().bold(),
|
file: data.profile.background,
|
||||||
Row(
|
)
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
: Container(
|
||||||
textBaseline: TextBaseline.alphabetic,
|
color:
|
||||||
spacing: 6,
|
Theme.of(
|
||||||
children: [
|
context,
|
||||||
Text(data.profile.timeZone),
|
).appBarTheme.backgroundColor,
|
||||||
Text(
|
),
|
||||||
getTzInfo(
|
),
|
||||||
data.profile.timeZone,
|
FlexibleSpaceBar(
|
||||||
).$2.formatCustomGlobal('HH:mm'),
|
title: Text(
|
||||||
),
|
data.nick,
|
||||||
Text(
|
style: TextStyle(
|
||||||
getTzInfo(
|
color:
|
||||||
data.profile.timeZone,
|
appbarColor.value ??
|
||||||
).$1.formatOffsetLocal(),
|
Theme.of(
|
||||||
).fontSize(11),
|
context,
|
||||||
Text(
|
).appBarTheme.foregroundColor,
|
||||||
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
|
shadows: [appbarShadow],
|
||||||
).fontSize(11).opacity(0.75),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
),
|
|
||||||
|
|
||||||
if (user.value != null)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: const Divider(
|
|
||||||
height: 1,
|
|
||||||
).padding(top: 24, bottom: 12),
|
|
||||||
),
|
|
||||||
if (user.value != null)
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Row(
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
if (accountRelationship.value == null ||
|
|
||||||
accountRelationship.value!.status > -100)
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton.icon(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? null
|
|
||||||
: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
foregroundColor: WidgetStatePropertyAll(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? null
|
|
||||||
: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onSecondary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: relationshipAction,
|
],
|
||||||
label:
|
|
||||||
Text(
|
|
||||||
accountRelationship.value == null
|
|
||||||
? 'addFriendShort'
|
|
||||||
: 'added',
|
|
||||||
).tr(),
|
|
||||||
icon:
|
|
||||||
accountRelationship.value == null
|
|
||||||
? const Icon(Symbols.person_add)
|
|
||||||
: const Icon(Symbols.person_check),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (accountRelationship.value == null ||
|
),
|
||||||
accountRelationship.value!.status <= -100)
|
SliverToBoxAdapter(child: accountBasicInfo(data)),
|
||||||
Expanded(
|
if (data.badges.isNotEmpty)
|
||||||
child: FilledButton.icon(
|
SliverToBoxAdapter(
|
||||||
style: ButtonStyle(
|
child: BadgeList(
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
badges: data.badges,
|
||||||
accountRelationship.value == null
|
).padding(horizontal: 24, bottom: 24),
|
||||||
? null
|
),
|
||||||
: Theme.of(context).colorScheme.secondary,
|
SliverToBoxAdapter(
|
||||||
),
|
child: Column(
|
||||||
foregroundColor: WidgetStatePropertyAll(
|
spacing: 12,
|
||||||
accountRelationship.value == null
|
children: [
|
||||||
? null
|
LevelingProgressCard(
|
||||||
: Theme.of(
|
level: data.profile.level,
|
||||||
context,
|
experience: data.profile.experience,
|
||||||
).colorScheme.onSecondary,
|
progress: data.profile.levelingProgress,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onPressed: blockAction,
|
if (data.profile.verification != null)
|
||||||
label:
|
VerificationStatusCard(
|
||||||
Text(
|
mark: data.profile.verification!,
|
||||||
accountRelationship.value == null
|
),
|
||||||
? 'blockUser'
|
],
|
||||||
: 'unblockUser',
|
).padding(horizontal: 20),
|
||||||
).tr(),
|
),
|
||||||
icon:
|
|
||||||
accountRelationship.value == null
|
SliverToBoxAdapter(child: accountProfileDetail(data)),
|
||||||
? const Icon(Symbols.block)
|
|
||||||
: const Icon(Symbols.person_cancel),
|
if (user.value != null)
|
||||||
),
|
SliverToBoxAdapter(child: accountAction(data)),
|
||||||
),
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
FortuneGraphWidget(
|
||||||
|
events: accountEvents,
|
||||||
|
eventCalanderUser: data.name,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 8),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16),
|
),
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Row(
|
|
||||||
spacing: 8,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: FilledButton.icon(
|
|
||||||
onPressed: directMessageAction,
|
|
||||||
icon: const Icon(Symbols.message),
|
|
||||||
label:
|
|
||||||
Text(
|
|
||||||
accountChat.value == null
|
|
||||||
? 'createDirectMessage'
|
|
||||||
: 'gotoDirectMessage',
|
|
||||||
maxLines: 1,
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton.filled(
|
|
||||||
onPressed: () {
|
|
||||||
showAbuseReportSheet(
|
|
||||||
context,
|
|
||||||
resourceIdentifier: 'account/${data.id}',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Symbols.flag,
|
|
||||||
color: Theme.of(context).colorScheme.onError,
|
|
||||||
),
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStatePropertyAll(
|
|
||||||
Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 16, top: 4),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: const Divider(height: 1).padding(top: 12),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
FortuneGraphWidget(
|
|
||||||
events: accountEvents,
|
|
||||||
eventCalanderUser: data.name,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(all: 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
error:
|
error:
|
||||||
(error, stackTrace) => AppScaffold(
|
(error, stackTrace) => AppScaffold(
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class CreateAccountScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: const PageBackButton(),
|
leading: const PageBackButton(),
|
||||||
title: Text('createAccount').tr(),
|
title: Text('createAccount').tr(),
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class LoginScreen extends HookConsumerWidget {
|
|||||||
final factorPicked = useState<SnAuthFactor?>(null);
|
final factorPicked = useState<SnAuthFactor?>(null);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: const PageBackButton(),
|
leading: const PageBackButton(),
|
||||||
title: Text('login').tr(),
|
title: Text('login').tr(),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/chat/call_button.dart';
|
import 'package:island/widgets/chat/call_button.dart';
|
||||||
import 'package:island/widgets/chat/call_overlay.dart';
|
import 'package:island/widgets/chat/call_overlay.dart';
|
||||||
@@ -21,17 +20,27 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final ongoingCall = ref.watch(ongoingCallProvider(roomId));
|
final ongoingCall = ref.watch(ongoingCallProvider(roomId));
|
||||||
final callState = ref.watch(callNotifierProvider);
|
final callState = ref.watch(callNotifierProvider);
|
||||||
final callNotifier = ref.read(callNotifierProvider.notifier);
|
final callNotifier = ref.watch(callNotifierProvider.notifier);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
callNotifier.joinRoom(roomId);
|
callNotifier.joinRoom(roomId);
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
final viewMode = useState<String>('grid');
|
final allAudioOnly = callNotifier.participants.every(
|
||||||
|
(p) =>
|
||||||
|
!(p.hasVideo &&
|
||||||
|
p.remoteParticipant.trackPublications.values.any(
|
||||||
|
(pub) =>
|
||||||
|
pub.track != null &&
|
||||||
|
pub.kind == TrackType.VIDEO &&
|
||||||
|
!pub.muted &&
|
||||||
|
!pub.isDisposed,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: PageBackButton(),
|
leading: PageBackButton(),
|
||||||
title: Column(
|
title: Column(
|
||||||
@@ -44,45 +53,50 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
Text(
|
Text(
|
||||||
callState.isConnected
|
callState.isConnected
|
||||||
? formatDuration(callState.duration)
|
? formatDuration(callState.duration)
|
||||||
: 'Connecting',
|
: 'connecting',
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Row(
|
if (!allAudioOnly)
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
SingleChildScrollView(
|
||||||
children: [
|
child: Row(
|
||||||
IconButton(
|
spacing: 4,
|
||||||
icon: Icon(Symbols.grid_view),
|
children: [
|
||||||
tooltip: 'Grid View',
|
for (final live in callNotifier.participants)
|
||||||
onPressed: () => viewMode.value = 'grid',
|
SpeakingRippleAvatar(live: live, size: 30),
|
||||||
color:
|
const Gap(8),
|
||||||
viewMode.value == 'grid'
|
],
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
icon: Icon(Symbols.view_agenda),
|
|
||||||
tooltip: 'Stage View',
|
|
||||||
onPressed: () => viewMode.value = 'stage',
|
|
||||||
color:
|
|
||||||
viewMode.value == 'stage'
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body:
|
body:
|
||||||
callState.error != null
|
callState.error != null
|
||||||
? Center(
|
? Center(
|
||||||
child: Text(
|
child: ConstrainedBox(
|
||||||
callState.error!,
|
constraints: const BoxConstraints(maxWidth: 320),
|
||||||
textAlign: TextAlign.center,
|
child: Column(
|
||||||
style: const TextStyle(color: Colors.red),
|
children: [
|
||||||
|
const Icon(Symbols.error_outline, size: 48),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
callState.error!,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(color: Color(0xFF757575)),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
callNotifier.disconnect();
|
||||||
|
callNotifier.dispose();
|
||||||
|
callNotifier.joinRoom(roomId);
|
||||||
|
},
|
||||||
|
child: Text('retry').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
@@ -100,17 +114,8 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
child: Text('No participants in call'),
|
child: Text('No participants in call'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final participants = callNotifier.participants;
|
final participants = callNotifier.participants;
|
||||||
final allAudioOnly = participants.every(
|
|
||||||
(p) =>
|
|
||||||
!(p.hasVideo &&
|
|
||||||
p.remoteParticipant.trackPublications.values
|
|
||||||
.any(
|
|
||||||
(pub) =>
|
|
||||||
pub.track != null &&
|
|
||||||
pub.kind == TrackType.VIDEO,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
if (allAudioOnly) {
|
if (allAudioOnly) {
|
||||||
// Audio-only: show avatars in a compact row
|
// Audio-only: show avatars in a compact row
|
||||||
return Center(
|
return Center(
|
||||||
@@ -123,138 +128,41 @@ class CallScreen extends HookConsumerWidget {
|
|||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
for (final live in participants)
|
for (final live in participants)
|
||||||
Padding(
|
SpeakingRippleAvatar(
|
||||||
padding: const EdgeInsets.symmetric(
|
live: live,
|
||||||
horizontal: 8,
|
size: 72,
|
||||||
),
|
).padding(horizontal: 4),
|
||||||
child: SpeakingRippleAvatar(
|
|
||||||
isSpeaking: live.isSpeaking,
|
|
||||||
audioLevel:
|
|
||||||
live.remoteParticipant.audioLevel,
|
|
||||||
pictureId:
|
|
||||||
live
|
|
||||||
.participant
|
|
||||||
.profile
|
|
||||||
?.account
|
|
||||||
.profile
|
|
||||||
.picture
|
|
||||||
?.id,
|
|
||||||
size: 72,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (viewMode.value == 'stage') {
|
|
||||||
// Stage view: show main speaker(s) large, others in row
|
// Stage view: show main speaker(s) large, others in row
|
||||||
final mainSpeakers =
|
final mainSpeakers =
|
||||||
participants
|
participants
|
||||||
.where(
|
.where(
|
||||||
(p) => p
|
(p) => p
|
||||||
.remoteParticipant
|
.remoteParticipant
|
||||||
.trackPublications
|
.trackPublications
|
||||||
.values
|
.values
|
||||||
.any(
|
.any(
|
||||||
(pub) =>
|
(pub) =>
|
||||||
pub.track != null &&
|
pub.track != null &&
|
||||||
pub.kind == TrackType.VIDEO,
|
pub.kind == TrackType.VIDEO,
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
if (mainSpeakers.isEmpty && participants.isNotEmpty) {
|
|
||||||
mainSpeakers.add(participants.first);
|
|
||||||
}
|
|
||||||
final others =
|
|
||||||
participants
|
|
||||||
.where((p) => !mainSpeakers.contains(p))
|
|
||||||
.toList();
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
for (final speaker in mainSpeakers)
|
|
||||||
Expanded(
|
|
||||||
child:
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: 16 / 9,
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(8),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
CallParticipantTile(
|
|
||||||
live: speaker,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).center(),
|
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
).padding(horizontal: 12),
|
.toList();
|
||||||
),
|
if (mainSpeakers.isEmpty && participants.isNotEmpty) {
|
||||||
if (others.isNotEmpty)
|
mainSpeakers.add(participants.first);
|
||||||
SizedBox(
|
|
||||||
height: 100,
|
|
||||||
child: ListView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
children: [
|
|
||||||
for (final other in others)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8,
|
|
||||||
),
|
|
||||||
child: CallParticipantTile(
|
|
||||||
live: other,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// Default: grid view
|
return Column(
|
||||||
return GridView.builder(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(
|
for (final speaker in mainSpeakers)
|
||||||
horizontal: 12,
|
Expanded(
|
||||||
vertical: 8,
|
child: CallParticipantTile(live: speaker),
|
||||||
),
|
|
||||||
gridDelegate:
|
|
||||||
SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount:
|
|
||||||
isWidestScreen(context)
|
|
||||||
? 4
|
|
||||||
: isWiderScreen(context)
|
|
||||||
? 3
|
|
||||||
: 2,
|
|
||||||
childAspectRatio: 16 / 9,
|
|
||||||
crossAxisSpacing: 8,
|
|
||||||
mainAxisSpacing: 8,
|
|
||||||
),
|
),
|
||||||
itemCount: participants.length,
|
],
|
||||||
itemBuilder: (context, idx) {
|
|
||||||
final live = participants[idx];
|
|
||||||
return AspectRatio(
|
|
||||||
aspectRatio: 16 / 9,
|
|
||||||
child: Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: Column(
|
|
||||||
children: [CallParticipantTile(live: live)],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).center();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import 'package:island/services/responsive.dart';
|
|||||||
import 'package:island/widgets/account/account_picker.dart';
|
import 'package:island/widgets/account/account_picker.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/chat/call_overlay.dart';
|
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/realms/selection_dropdown.dart';
|
import 'package:island/widgets/realms/selection_dropdown.dart';
|
||||||
@@ -346,91 +345,79 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
child: const Icon(Symbols.add),
|
child: const Icon(Symbols.add),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||||
body: Stack(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Consumer(
|
||||||
children: [
|
builder: (context, ref, _) {
|
||||||
Consumer(
|
final summaryState = ref.watch(chatSummaryProvider);
|
||||||
builder: (context, ref, _) {
|
return summaryState.maybeWhen(
|
||||||
final summaryState = ref.watch(chatSummaryProvider);
|
loading:
|
||||||
return summaryState.maybeWhen(
|
() => const LinearProgressIndicator(
|
||||||
loading:
|
minHeight: 2,
|
||||||
() => const LinearProgressIndicator(
|
borderRadius: BorderRadius.zero,
|
||||||
minHeight: 2,
|
),
|
||||||
borderRadius: BorderRadius.zero,
|
orElse: () => const SizedBox.shrink(),
|
||||||
),
|
);
|
||||||
orElse: () => const SizedBox.shrink(),
|
},
|
||||||
);
|
),
|
||||||
},
|
Expanded(
|
||||||
),
|
child: chats.when(
|
||||||
Expanded(
|
data:
|
||||||
child: chats.when(
|
(items) => RefreshIndicator(
|
||||||
data:
|
onRefresh:
|
||||||
(items) => RefreshIndicator(
|
() => Future.sync(() {
|
||||||
onRefresh:
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
() => Future.sync(() {
|
}),
|
||||||
ref.invalidate(chatroomsJoinedProvider);
|
child: ListView.builder(
|
||||||
}),
|
padding: getTabbedPadding(
|
||||||
child: ListView.builder(
|
context,
|
||||||
padding: getTabbedPadding(
|
bottom: callState.isConnected ? 96 : null,
|
||||||
context,
|
),
|
||||||
bottom: callState.isConnected ? 96 : null,
|
itemCount:
|
||||||
),
|
items
|
||||||
itemCount:
|
.where(
|
||||||
items
|
(item) =>
|
||||||
.where(
|
selectedTab.value == 0 ||
|
||||||
(item) =>
|
(selectedTab.value == 1 &&
|
||||||
selectedTab.value == 0 ||
|
item.type == 1) ||
|
||||||
(selectedTab.value == 1 &&
|
(selectedTab.value == 2 && item.type != 1),
|
||||||
item.type == 1) ||
|
)
|
||||||
(selectedTab.value == 2 &&
|
.length,
|
||||||
item.type != 1),
|
itemBuilder: (context, index) {
|
||||||
)
|
final filteredItems =
|
||||||
.length,
|
items
|
||||||
itemBuilder: (context, index) {
|
.where(
|
||||||
final filteredItems =
|
(item) =>
|
||||||
items
|
selectedTab.value == 0 ||
|
||||||
.where(
|
(selectedTab.value == 1 &&
|
||||||
(item) =>
|
item.type == 1) ||
|
||||||
selectedTab.value == 0 ||
|
(selectedTab.value == 2 &&
|
||||||
(selectedTab.value == 1 &&
|
item.type != 1),
|
||||||
item.type == 1) ||
|
)
|
||||||
(selectedTab.value == 2 &&
|
.toList();
|
||||||
item.type != 1),
|
final item = filteredItems[index];
|
||||||
)
|
return ChatRoomListTile(
|
||||||
.toList();
|
room: item,
|
||||||
final item = filteredItems[index];
|
isDirect: item.type == 1,
|
||||||
return ChatRoomListTile(
|
onTap: () {
|
||||||
room: item,
|
context.pushNamed(
|
||||||
isDirect: item.type == 1,
|
'chatRoom',
|
||||||
onTap: () {
|
pathParameters: {'id': item.id},
|
||||||
context.pushNamed(
|
|
||||||
'chatRoom',
|
|
||||||
pathParameters: {'id': item.id},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
loading:
|
),
|
||||||
() => const Center(child: CircularProgressIndicator()),
|
),
|
||||||
error:
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
(error, stack) => ResponseErrorWidget(
|
error:
|
||||||
error: error,
|
(error, stack) => ResponseErrorWidget(
|
||||||
onRetry: () {
|
error: error,
|
||||||
ref.invalidate(chatroomsJoinedProvider);
|
onRetry: () {
|
||||||
},
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: getTabbedPadding(context).bottom + 8,
|
|
||||||
child: const CallOverlayBar().padding(horizontal: 16, vertical: 12),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
try {
|
try {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
await client.patch(
|
await client.patch(
|
||||||
'/chat/$id/members/me/notify',
|
'/sphere/chat/$id/members/me/notify',
|
||||||
data: {'notify_level': level},
|
data: {'notify_level': level},
|
||||||
);
|
);
|
||||||
ref.invalidate(chatroomIdentityProvider(id));
|
ref.invalidate(chatroomIdentityProvider(id));
|
||||||
@@ -59,7 +59,7 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
try {
|
try {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
await client.patch(
|
await client.patch(
|
||||||
'/chat/$id/members/me/notify',
|
'/sphere/chat/$id/members/me/notify',
|
||||||
data: {'break_until': until.toUtc().toIso8601String()},
|
data: {'break_until': until.toUtc().toIso8601String()},
|
||||||
);
|
);
|
||||||
ref.invalidate(chatroomProvider(id));
|
ref.invalidate(chatroomProvider(id));
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ class CreatorHubShellScreen extends StatelessWidget {
|
|||||||
isRoot: true,
|
isRoot: true,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 360, child: const CreatorHubScreen(isAside: true)),
|
Flexible(flex: 2, child: const CreatorHubScreen(isAside: true)),
|
||||||
const VerticalDivider(width: 1),
|
const VerticalDivider(width: 1),
|
||||||
Expanded(child: child),
|
Flexible(flex: 3, child: child),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ part 'pack_detail.freezed.dart';
|
|||||||
@riverpod
|
@riverpod
|
||||||
Future<List<SnSticker>> stickerPackContent(Ref ref, String packId) async {
|
Future<List<SnSticker>> stickerPackContent(Ref ref, String packId) async {
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
final resp = await apiClient.get('/stickers/$packId/content');
|
final resp = await apiClient.get('/sphere/stickers/$packId/content');
|
||||||
return resp.data
|
return resp.data
|
||||||
.map<SnSticker>((e) => SnSticker.fromJson(e))
|
.map<SnSticker>((e) => SnSticker.fromJson(e))
|
||||||
.cast<SnSticker>()
|
.cast<SnSticker>()
|
||||||
@@ -74,13 +74,16 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.add_circle),
|
icon: const Icon(Symbols.add_circle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed('creatorStickerNew', pathParameters: {'packId': id}).then((
|
context
|
||||||
value,
|
.pushNamed(
|
||||||
) {
|
'creatorStickerNew',
|
||||||
if (value != null) {
|
pathParameters: {'name': pubName, 'packId': id},
|
||||||
ref.invalidate(stickerPackContentProvider(id));
|
)
|
||||||
}
|
.then((value) {
|
||||||
});
|
if (value != null) {
|
||||||
|
ref.invalidate(stickerPackContentProvider(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_StickerPackActionMenu(
|
_StickerPackActionMenu(
|
||||||
@@ -173,9 +176,13 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
title: 'edit'.tr(),
|
title: 'edit'.tr(),
|
||||||
image: MenuImage.icon(Symbols.edit),
|
image: MenuImage.icon(Symbols.edit),
|
||||||
callback: () {
|
callback: () {
|
||||||
context.pushNamed(
|
context
|
||||||
|
.pushNamed(
|
||||||
'creatorStickerEdit',
|
'creatorStickerEdit',
|
||||||
pathParameters: {'packId': id, 'id': sticker.id},
|
pathParameters: {
|
||||||
|
'packId': id,
|
||||||
|
'id': sticker.id,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.then((value) {
|
.then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -259,9 +266,7 @@ class _StickerPackActionMenu extends HookConsumerWidget {
|
|||||||
(context) => [
|
(context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.push(
|
context.push('/creators/$pubName/stickers/$packId/edit');
|
||||||
'/creators/$pubName/stickers/$packId/edit',
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'pack_detail.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$stickerPackContentHash() =>
|
String _$stickerPackContentHash() =>
|
||||||
r'78de848fba1f341f217f8ae4b9eef2d8afa67964';
|
r'42d74f51022e67e35cb601c2f30f4f02e1f2be9d';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget {
|
|||||||
context
|
context
|
||||||
.pushNamed(
|
.pushNamed(
|
||||||
'creatorStickerPackNew',
|
'creatorStickerPackNew',
|
||||||
queryParameters: {'pubName': pubName},
|
queryParameters: {'name': pubName},
|
||||||
)
|
)
|
||||||
.then((value) {
|
.then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -76,7 +76,7 @@ class SliverStickerPacksList extends HookConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed(
|
context.pushNamed(
|
||||||
'creatorStickerPackDetail',
|
'creatorStickerPackDetail',
|
||||||
pathParameters: {'pubName': pubName, 'packId': sticker.id},
|
pathParameters: {'name': pubName, 'packId': sticker.id},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -51,12 +51,9 @@ class DeveloperHubShellScreen extends StatelessWidget {
|
|||||||
isRoot: true,
|
isRoot: true,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Flexible(flex: 2, child: const DeveloperHubScreen(isAside: true)),
|
||||||
width: 360,
|
|
||||||
child: const DeveloperHubScreen(isAside: true),
|
|
||||||
),
|
|
||||||
const VerticalDivider(width: 1),
|
const VerticalDivider(width: 1),
|
||||||
Expanded(child: child),
|
Flexible(flex: 3, child: child),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -114,7 +111,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: !isWide ? const PageBackButton() : null,
|
leading: !isWide ? const PageBackButton() : null,
|
||||||
title: Text('developerHub').tr(),
|
title: Text('developerHub').tr(),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class DiscoveryRealmsScreen extends HookConsumerWidget {
|
|||||||
final currentQuery = useState<String?>(null);
|
final currentQuery = useState<String?>(null);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(title: Text('discoverRealms'.tr())),
|
appBar: AppBar(title: Text('discoverRealms'.tr())),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -84,8 +84,10 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
selectedDay.value = day;
|
selectedDay.value = day;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
toolbarHeight: 0,
|
toolbarHeight: 0,
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
@@ -167,67 +169,100 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: InkWell(
|
||||||
heroTag: Key("explore-page-fab"),
|
onLongPress: () {
|
||||||
onPressed: () {
|
context.pushNamed('postCompose', queryParameters: {'type': '1'}).then(
|
||||||
context.pushNamed('postCompose').then((value) {
|
(value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
activitiesNotifier.forceRefresh();
|
activitiesNotifier.forceRefresh();
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Icon(Symbols.edit),
|
child: FloatingActionButton(
|
||||||
|
heroTag: Key("explore-page-fab"),
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed('postCompose').then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
activitiesNotifier.forceRefresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(Symbols.edit),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||||
body: Builder(
|
body: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final isWider = isWiderScreen(context);
|
final isWider = isWiderScreen(context);
|
||||||
|
|
||||||
final bodyView = TabBarView(
|
final bodyView = _buildActivityList(
|
||||||
controller: tabController,
|
context,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
ref,
|
||||||
children: [
|
currentFilter.value,
|
||||||
_buildActivityList(context, ref, null),
|
|
||||||
_buildActivityList(context, ref, 'subscriptions'),
|
|
||||||
_buildActivityList(context, ref, 'friends'),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isWider) {
|
if (isWider) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(flex: 3, child: bodyView),
|
Flexible(flex: 3, child: bodyView.padding(left: 8)),
|
||||||
const VerticalDivider(width: 1),
|
if (user.value != null)
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
CheckInWidget(),
|
CheckInWidget(
|
||||||
Card(
|
margin: EdgeInsets.only(
|
||||||
margin: EdgeInsets.only(left: 16, right: 16, top: 8),
|
left: 8,
|
||||||
child: Column(
|
right: 12,
|
||||||
children: [
|
top: 16,
|
||||||
// Use the reusable EventCalendarWidget
|
),
|
||||||
EventCalendarWidget(
|
|
||||||
events: events,
|
|
||||||
initialDate: now,
|
|
||||||
showEventDetails: true,
|
|
||||||
onMonthChanged: onMonthChanged,
|
|
||||||
onDaySelected: onDaySelected,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Card(
|
||||||
FortuneGraphWidget(
|
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
||||||
events: events,
|
child: Column(
|
||||||
constrainWidth: true,
|
children: [
|
||||||
onPointSelected: onDaySelected,
|
// Use the reusable EventCalendarWidget
|
||||||
|
EventCalendarWidget(
|
||||||
|
events: events,
|
||||||
|
initialDate: now,
|
||||||
|
showEventDetails: true,
|
||||||
|
onMonthChanged: onMonthChanged,
|
||||||
|
onDaySelected: onDaySelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FortuneGraphWidget(
|
||||||
|
margin: EdgeInsets.only(left: 8, right: 12, top: 8),
|
||||||
|
events: events,
|
||||||
|
constrainWidth: true,
|
||||||
|
onPointSelected: onDaySelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Flexible(
|
||||||
|
flex: 2,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Welcome to\nthe Solar Network',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
).bold(),
|
||||||
|
const Gap(2),
|
||||||
|
Text(
|
||||||
|
'Login to explore more!',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
).padding(horizontal: 36, vertical: 16),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -280,56 +315,62 @@ class _DiscoveryActivityItem extends StatelessWidget {
|
|||||||
final items = data['items'] as List;
|
final items = data['items'] as List;
|
||||||
final type = items.firstOrNull?['type'] ?? 'unknown';
|
final type = items.firstOrNull?['type'] ?? 'unknown';
|
||||||
|
|
||||||
return Column(
|
return Card(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
children: [
|
child: Column(
|
||||||
Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
const Icon(Symbols.explore, size: 19),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
const Gap(8),
|
children: [
|
||||||
Text(
|
const Icon(Symbols.explore, size: 19),
|
||||||
(switch (type) {
|
const Gap(8),
|
||||||
'realm' => 'discoverRealms',
|
Text(
|
||||||
'publisher' => 'discoverPublishers',
|
(switch (type) {
|
||||||
'article' => 'discoverWebArticles',
|
'realm' => 'discoverRealms',
|
||||||
_ => 'unknown',
|
'publisher' => 'discoverPublishers',
|
||||||
}).tr(),
|
'article' => 'discoverWebArticles',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
_ => 'unknown',
|
||||||
).padding(top: 1),
|
}).tr(),
|
||||||
],
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
).padding(horizontal: 20, top: 8, bottom: 4),
|
).padding(top: 1),
|
||||||
SizedBox(
|
],
|
||||||
height: 180,
|
).padding(horizontal: 20, top: 8, bottom: 4),
|
||||||
child: ListView.builder(
|
SizedBox(
|
||||||
scrollDirection: Axis.horizontal,
|
height: 180,
|
||||||
itemCount: items.length,
|
child: ConstrainedBox(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
constraints: const BoxConstraints(maxHeight: 200),
|
||||||
itemBuilder: (context, index) {
|
child: CarouselView.weighted(
|
||||||
final item = items[index];
|
flexWeights:
|
||||||
switch (type) {
|
isWideScreen(context) ? <int>[3, 2, 1] : <int>[4, 1],
|
||||||
case 'realm':
|
consumeMaxWeight: false,
|
||||||
return RealmCard(
|
enableSplash: false,
|
||||||
realm: SnRealm.fromJson(item['data']),
|
shape: RoundedRectangleBorder(
|
||||||
maxWidth: 280,
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
);
|
),
|
||||||
case 'publisher':
|
children: [
|
||||||
return PublisherCard(
|
for (final item in items)
|
||||||
publisher: SnPublisher.fromJson(item['data']),
|
switch (type) {
|
||||||
maxWidth: 280,
|
'realm' => RealmCard(
|
||||||
);
|
realm: SnRealm.fromJson(item['data']),
|
||||||
case 'article':
|
maxWidth: 280,
|
||||||
return WebArticleCard(
|
),
|
||||||
article: SnWebArticle.fromJson(item['data']),
|
'publisher' => PublisherCard(
|
||||||
maxWidth: 280,
|
publisher: SnPublisher.fromJson(item['data']),
|
||||||
);
|
maxWidth: 280,
|
||||||
default:
|
),
|
||||||
return Placeholder();
|
'article' => WebArticleCard(
|
||||||
}
|
article: SnWebArticle.fromJson(item['data']),
|
||||||
},
|
maxWidth: 280,
|
||||||
),
|
),
|
||||||
).padding(bottom: 4),
|
_ => Placeholder(),
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).padding(bottom: 8, horizontal: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,8 +396,13 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
|
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
|
SliverGap(12),
|
||||||
if (user.value != null && !contentOnly)
|
if (user.value != null && !contentOnly)
|
||||||
SliverToBoxAdapter(child: CheckInWidget()),
|
SliverToBoxAdapter(
|
||||||
|
child: CheckInWidget(
|
||||||
|
margin: EdgeInsets.only(left: 8, right: 8, bottom: 4),
|
||||||
|
),
|
||||||
|
),
|
||||||
SliverList.builder(
|
SliverList.builder(
|
||||||
itemCount: widgetCount,
|
itemCount: widgetCount,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@@ -373,19 +419,9 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'posts.new':
|
case 'posts.new':
|
||||||
case 'posts.new.replies':
|
case 'posts.new.replies':
|
||||||
final isReply = item.type == 'posts.new.replies';
|
itemWidget = PostActionableItem(
|
||||||
itemWidget = PostItem(
|
borderRadius: 8,
|
||||||
backgroundColor:
|
|
||||||
isWideScreen(context) ? Colors.transparent : null,
|
|
||||||
item: SnPost.fromJson(item.data!),
|
item: SnPost.fromJson(item.data!),
|
||||||
padding:
|
|
||||||
isReply
|
|
||||||
? const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
bottom: 16,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
onRefresh: () {
|
onRefresh: () {
|
||||||
activitiesNotifier.forceRefresh();
|
activitiesNotifier.forceRefresh();
|
||||||
},
|
},
|
||||||
@@ -396,21 +432,10 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (isReply) {
|
itemWidget = Card(
|
||||||
itemWidget = Column(
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: itemWidget,
|
||||||
children: [
|
);
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.reply),
|
|
||||||
const Gap(8),
|
|
||||||
Text('Replying your post'),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 20, vertical: 8),
|
|
||||||
itemWidget,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'discovery':
|
case 'discovery':
|
||||||
itemWidget = _DiscoveryActivityItem(data: item.data!);
|
itemWidget = _DiscoveryActivityItem(data: item.data!);
|
||||||
@@ -419,7 +444,7 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
itemWidget = const Placeholder();
|
itemWidget = const Placeholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(children: [itemWidget, const Divider(height: 1)]);
|
return itemWidget;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SliverGap(getTabbedPadding(context).bottom),
|
SliverGap(getTabbedPadding(context).bottom),
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import 'package:island/widgets/post/publishers_modal.dart';
|
|||||||
import 'package:island/screens/posts/post_detail.dart';
|
import 'package:island/screens/posts/post_detail.dart';
|
||||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||||
import 'package:island/services/compose_storage_db.dart';
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
import 'package:island/widgets/post/draft_manager.dart';
|
// DraftManagerSheet is now imported through compose_toolbar.dart
|
||||||
|
import 'package:island/widgets/post/compose_toolbar.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@@ -52,13 +53,13 @@ class PostEditScreen extends HookConsumerWidget {
|
|||||||
data: (post) => PostComposeScreen(originalPost: post),
|
data: (post) => PostComposeScreen(originalPost: post),
|
||||||
loading:
|
loading:
|
||||||
() => AppScaffold(
|
() => AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(leading: const PageBackButton()),
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
body: const Center(child: CircularProgressIndicator()),
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
),
|
),
|
||||||
error:
|
error:
|
||||||
(e, _) => AppScaffold(
|
(e, _) => AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(leading: const PageBackButton()),
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
body: Text('Error: $e', textAlign: TextAlign.center),
|
body: Text('Error: $e', textAlign: TextAlign.center),
|
||||||
),
|
),
|
||||||
@@ -92,7 +93,6 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Otherwise, continue with regular post compose
|
// Otherwise, continue with regular post compose
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
|
||||||
|
|
||||||
// When editing, preserve the original replied/forwarded post references
|
// When editing, preserve the original replied/forwarded post references
|
||||||
final effectiveRepliedPost = repliedPost ?? originalPost?.repliedPost;
|
final effectiveRepliedPost = repliedPost ?? originalPost?.repliedPost;
|
||||||
@@ -287,43 +287,10 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: AppScaffold(
|
child: AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: const PageBackButton(),
|
leading: const PageBackButton(),
|
||||||
actions: [
|
actions: [
|
||||||
if (originalPost == null) // Only show drafts for new posts
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Symbols.draft),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
builder:
|
|
||||||
(context) => DraftManagerSheet(
|
|
||||||
onDraftSelected: (draftId) {
|
|
||||||
final draft =
|
|
||||||
ref.read(
|
|
||||||
composeStorageNotifierProvider,
|
|
||||||
)[draftId];
|
|
||||||
if (draft != null) {
|
|
||||||
state.titleController.text = draft.title ?? '';
|
|
||||||
state.descriptionController.text =
|
|
||||||
draft.description ?? '';
|
|
||||||
state.contentController.text =
|
|
||||||
draft.content ?? '';
|
|
||||||
state.visibility.value = draft.visibility;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
tooltip: 'drafts'.tr(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Symbols.save),
|
|
||||||
onPressed: () => ComposeLogic.saveDraft(ref, state),
|
|
||||||
tooltip: 'saveDraft'.tr(),
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.settings),
|
icon: const Icon(Symbols.settings),
|
||||||
onPressed: showSettingsSheet,
|
onPressed: showSettingsSheet,
|
||||||
@@ -455,27 +422,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Bottom toolbar
|
// Bottom toolbar
|
||||||
Material(
|
ComposeToolbar(state: state, originalPost: originalPost),
|
||||||
elevation: 4,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => ComposeLogic.pickPhotoMedia(ref, state),
|
|
||||||
icon: const Icon(Symbols.add_a_photo),
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => ComposeLogic.pickVideoMedia(ref, state),
|
|
||||||
icon: const Icon(Symbols.videocam),
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 16,
|
|
||||||
horizontal: 16,
|
|
||||||
top: 8,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -650,7 +597,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: PostItem(item: post, isOpenable: false),
|
child: PostItem(item: post),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import 'package:island/widgets/content/markdown.dart';
|
|||||||
import 'package:island/widgets/post/compose_shared.dart';
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||||
import 'package:island/services/compose_storage_db.dart';
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
|
import 'package:island/widgets/post/compose_toolbar.dart';
|
||||||
import 'package:island/widgets/post/publishers_modal.dart';
|
import 'package:island/widgets/post/publishers_modal.dart';
|
||||||
import 'package:island/widgets/post/draft_manager.dart';
|
|
||||||
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@@ -153,6 +153,57 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildPreviewPane() {
|
Widget buildPreviewPane() {
|
||||||
|
final widgetItem = SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 8),
|
||||||
|
child: ValueListenableBuilder<TextEditingValue>(
|
||||||
|
valueListenable: state.titleController,
|
||||||
|
builder: (context, titleValue, _) {
|
||||||
|
return ValueListenableBuilder<TextEditingValue>(
|
||||||
|
valueListenable: state.descriptionController,
|
||||||
|
builder: (context, descriptionValue, _) {
|
||||||
|
return ValueListenableBuilder<TextEditingValue>(
|
||||||
|
valueListenable: state.contentController,
|
||||||
|
builder: (context, contentValue, _) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (titleValue.text.isNotEmpty) ...[
|
||||||
|
Text(
|
||||||
|
titleValue.text,
|
||||||
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
|
if (descriptionValue.text.isNotEmpty) ...[
|
||||||
|
Text(
|
||||||
|
descriptionValue.text,
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: colorScheme.onSurface.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
|
if (contentValue.text.isNotEmpty)
|
||||||
|
MarkdownTextContent(
|
||||||
|
content: contentValue.text,
|
||||||
|
textStyle: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isWideScreen(context)) {
|
||||||
|
return Align(alignment: Alignment.topLeft, child: widgetItem);
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: colorScheme.outline.withOpacity(0.3)),
|
border: Border.all(color: colorScheme.outline.withOpacity(0.3)),
|
||||||
@@ -178,210 +229,119 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(child: widgetItem),
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: ValueListenableBuilder<TextEditingValue>(
|
|
||||||
valueListenable: state.titleController,
|
|
||||||
builder: (context, titleValue, _) {
|
|
||||||
return ValueListenableBuilder<TextEditingValue>(
|
|
||||||
valueListenable: state.descriptionController,
|
|
||||||
builder: (context, descriptionValue, _) {
|
|
||||||
return ValueListenableBuilder<TextEditingValue>(
|
|
||||||
valueListenable: state.contentController,
|
|
||||||
builder: (context, contentValue, _) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (titleValue.text.isNotEmpty) ...[
|
|
||||||
Text(
|
|
||||||
titleValue.text,
|
|
||||||
style: theme.textTheme.headlineSmall
|
|
||||||
?.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
],
|
|
||||||
if (descriptionValue.text.isNotEmpty) ...[
|
|
||||||
Text(
|
|
||||||
descriptionValue.text,
|
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
|
||||||
color: colorScheme.onSurface.withOpacity(
|
|
||||||
0.7,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
],
|
|
||||||
if (contentValue.text.isNotEmpty)
|
|
||||||
MarkdownTextContent(
|
|
||||||
content: contentValue.text,
|
|
||||||
textStyle: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildEditorPane() {
|
Widget buildEditorPane() {
|
||||||
return Column(
|
return Center(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: ConstrainedBox(
|
||||||
children: [
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
// Publisher row
|
child: Column(
|
||||||
Card(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
margin: EdgeInsets.only(top: 8),
|
children: [
|
||||||
elevation: 1,
|
Expanded(
|
||||||
child: Padding(
|
child: RawKeyboardListener(
|
||||||
padding: const EdgeInsets.all(12),
|
focusNode: FocusNode(),
|
||||||
child: Row(
|
onKey:
|
||||||
children: [
|
(event) => _handleKeyPress(
|
||||||
GestureDetector(
|
event,
|
||||||
child: ProfilePictureWidget(
|
state,
|
||||||
fileId: state.currentPublisher.value?.picture?.id,
|
ref,
|
||||||
radius: 20,
|
context,
|
||||||
fallbackIcon:
|
originalPost: originalPost,
|
||||||
state.currentPublisher.value == null
|
),
|
||||||
? Symbols.question_mark
|
child: TextField(
|
||||||
: null,
|
controller: state.contentController,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: 'postContent'.tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
maxLines: null,
|
||||||
showModalBottomSheet(
|
expands: true,
|
||||||
isScrollControlled: true,
|
textAlignVertical: TextAlignVertical.top,
|
||||||
context: context,
|
onTapOutside:
|
||||||
builder: (context) => const PublisherModal(),
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).then((value) {
|
|
||||||
if (value != null) {
|
|
||||||
state.currentPublisher.value = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const Gap(16),
|
|
||||||
if (state.currentPublisher.value == null)
|
|
||||||
Text(
|
|
||||||
'postPublisherUnselected'.tr(),
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(state.currentPublisher.value!.nick).bold(),
|
|
||||||
Text(
|
|
||||||
'@${state.currentPublisher.value!.name}',
|
|
||||||
).fontSize(12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Content field with keyboard listener
|
|
||||||
Expanded(
|
|
||||||
child: RawKeyboardListener(
|
|
||||||
focusNode: FocusNode(),
|
|
||||||
onKey:
|
|
||||||
(event) => _handleKeyPress(
|
|
||||||
event,
|
|
||||||
state,
|
|
||||||
ref,
|
|
||||||
context,
|
|
||||||
originalPost: originalPost,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
controller: state.contentController,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintText: 'postContent'.tr(),
|
|
||||||
contentPadding: const EdgeInsets.all(8),
|
|
||||||
),
|
),
|
||||||
maxLines: null,
|
|
||||||
expands: true,
|
|
||||||
textAlignVertical: TextAlignVertical.top,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Attachments preview
|
// Attachments preview
|
||||||
ValueListenableBuilder<List<UniversalFile>>(
|
ValueListenableBuilder<List<UniversalFile>>(
|
||||||
valueListenable: state.attachments,
|
valueListenable: state.attachments,
|
||||||
builder: (context, attachments, _) {
|
builder: (context, attachments, _) {
|
||||||
if (attachments.isEmpty) return const SizedBox.shrink();
|
if (attachments.isEmpty) return const SizedBox.shrink();
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text(
|
Text(
|
||||||
'articleAttachmentHint'.tr(),
|
'articleAttachmentHint'.tr(),
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
ValueListenableBuilder<Map<int, double>>(
|
ValueListenableBuilder<Map<int, double>>(
|
||||||
valueListenable: state.attachmentProgress,
|
valueListenable: state.attachmentProgress,
|
||||||
builder: (context, progressMap, _) {
|
builder: (context, progressMap, _) {
|
||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
for (var idx = 0; idx < attachments.length; idx++)
|
for (var idx = 0; idx < attachments.length; idx++)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 280,
|
width: 280,
|
||||||
height: 280,
|
height: 280,
|
||||||
child: AttachmentPreview(
|
child: AttachmentPreview(
|
||||||
item: attachments[idx],
|
item: attachments[idx],
|
||||||
progress: progressMap[idx],
|
progress: progressMap[idx],
|
||||||
onRequestUpload:
|
onRequestUpload:
|
||||||
() => ComposeLogic.uploadAttachment(
|
() => ComposeLogic.uploadAttachment(
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
idx,
|
idx,
|
||||||
),
|
),
|
||||||
onDelete:
|
onDelete:
|
||||||
() => ComposeLogic.deleteAttachment(
|
() => ComposeLogic.deleteAttachment(
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
idx,
|
idx,
|
||||||
),
|
),
|
||||||
onMove: (delta) {
|
onMove: (delta) {
|
||||||
state
|
state
|
||||||
.attachments
|
.attachments
|
||||||
.value = ComposeLogic.moveAttachment(
|
.value = ComposeLogic.moveAttachment(
|
||||||
state.attachments.value,
|
state.attachments.value,
|
||||||
idx,
|
idx,
|
||||||
delta,
|
delta,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onInsert:
|
onInsert:
|
||||||
() => ComposeLogic.insertAttachment(
|
() => ComposeLogic.insertAttachment(
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
idx,
|
idx,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +352,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: AppScaffold(
|
child: AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: const PageBackButton(),
|
leading: const PageBackButton(),
|
||||||
title: ValueListenableBuilder<TextEditingValue>(
|
title: ValueListenableBuilder<TextEditingValue>(
|
||||||
@@ -406,38 +366,26 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
// Info banner for article compose
|
// Info banner for article compose
|
||||||
const SizedBox.shrink(),
|
const SizedBox.shrink(),
|
||||||
if (originalPost == null) // Only show drafts for new articles
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Symbols.draft),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
builder:
|
|
||||||
(context) => DraftManagerSheet(
|
|
||||||
onDraftSelected: (draftId) {
|
|
||||||
final draft =
|
|
||||||
ref.read(
|
|
||||||
composeStorageNotifierProvider,
|
|
||||||
)[draftId];
|
|
||||||
if (draft != null) {
|
|
||||||
state.titleController.text = draft.title ?? '';
|
|
||||||
state.descriptionController.text =
|
|
||||||
draft.description ?? '';
|
|
||||||
state.contentController.text =
|
|
||||||
draft.content ?? '';
|
|
||||||
state.visibility.value = draft.visibility;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
tooltip: 'drafts'.tr(),
|
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.save),
|
icon: ProfilePictureWidget(
|
||||||
onPressed: () => ComposeLogic.saveDraft(ref, state),
|
fileId: state.currentPublisher.value?.picture?.id,
|
||||||
tooltip: 'saveDraft'.tr(),
|
radius: 12,
|
||||||
|
fallbackIcon:
|
||||||
|
state.currentPublisher.value == null
|
||||||
|
? Symbols.question_mark
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const PublisherModal(),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
state.currentPublisher.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.settings),
|
icon: const Icon(Symbols.settings),
|
||||||
@@ -499,6 +447,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
flex: showPreview.value ? 1 : 2,
|
flex: showPreview.value ? 1 : 2,
|
||||||
child: buildEditorPane(),
|
child: buildEditorPane(),
|
||||||
),
|
),
|
||||||
|
const VerticalDivider(),
|
||||||
if (showPreview.value)
|
if (showPreview.value)
|
||||||
Expanded(child: buildPreviewPane()),
|
Expanded(child: buildPreviewPane()),
|
||||||
],
|
],
|
||||||
@@ -510,27 +459,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Bottom toolbar
|
// Bottom toolbar
|
||||||
Material(
|
ComposeToolbar(state: state, originalPost: originalPost),
|
||||||
elevation: 4,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => ComposeLogic.pickPhotoMedia(ref, state),
|
|
||||||
icon: const Icon(Symbols.add_a_photo),
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => ComposeLogic.pickVideoMedia(ref, state),
|
|
||||||
icon: const Icon(Symbols.videocam),
|
|
||||||
color: colorScheme.primary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 16,
|
|
||||||
horizontal: 16,
|
|
||||||
top: 8,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/post/post_quick_reply.dart';
|
import 'package:island/widgets/post/post_quick_reply.dart';
|
||||||
@@ -54,10 +53,8 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
final postState = ref.watch(postStateProvider(id));
|
final postState = ref.watch(postStateProvider(id));
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
final isWide = isWideScreen(context);
|
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(title: const Text('Post')),
|
appBar: AppBar(title: const Text('Post')),
|
||||||
body: postState.when(
|
body: postState.when(
|
||||||
data: (post) {
|
data: (post) {
|
||||||
@@ -67,13 +64,13 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Column(
|
child: Center(
|
||||||
children: [
|
child: ConstrainedBox(
|
||||||
PostItem(
|
constraints: BoxConstraints(maxWidth: 600),
|
||||||
|
child: PostItem(
|
||||||
item: post!,
|
item: post!,
|
||||||
isOpenable: false,
|
|
||||||
isFullPost: true,
|
isFullPost: true,
|
||||||
backgroundColor: isWide ? Colors.transparent : null,
|
isEmbedReply: false,
|
||||||
onUpdate: (newItem) {
|
onUpdate: (newItem) {
|
||||||
// Update the local state with the new post data
|
// Update the local state with the new post data
|
||||||
ref
|
ref
|
||||||
@@ -81,11 +78,10 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
.updatePost(newItem);
|
.updatePost(newItem);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PostRepliesList(postId: id),
|
PostRepliesList(postId: id, maxWidth: 600),
|
||||||
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
SliverGap(MediaQuery.of(context).padding.bottom + 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:island/models/post.dart';
|
|||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
|
import 'package:island/widgets/response.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
|
|
||||||
final postSearchNotifierProvider = StateNotifierProvider.autoDispose<
|
final postSearchNotifierProvider = StateNotifierProvider.autoDispose<
|
||||||
@@ -55,7 +56,7 @@ class PostSearchNotifier
|
|||||||
'query': _currentQuery,
|
'query': _currentQuery,
|
||||||
'offset': offset,
|
'offset': offset,
|
||||||
'take': _pageSize,
|
'take': _pageSize,
|
||||||
'useVector': true,
|
'useVector': false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: TextField(
|
title: TextField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
@@ -141,6 +142,7 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
itemCount: data.items.length + (data.hasMore ? 1 : 0),
|
itemCount: data.items.length + (data.hasMore ? 1 : 0),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index >= data.items.length) {
|
if (index >= data.items.length) {
|
||||||
@@ -151,14 +153,27 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final post = data.items[index];
|
final post = data.items[index];
|
||||||
return Column(
|
return Center(
|
||||||
children: [PostItem(item: post), const Divider(height: 1)],
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: 600),
|
||||||
|
child: Card(
|
||||||
|
margin: EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
child: PostActionableItem(item: post, borderRadius: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
error:
|
||||||
|
(error, stack) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry: () => ref.invalidate(postSearchNotifierProvider),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:island/models/user.dart';
|
|||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/services/color.dart';
|
import 'package:island/services/color.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/account/badge.dart';
|
import 'package:island/widgets/account/badge.dart';
|
||||||
import 'package:island/widgets/account/status.dart';
|
import 'package:island/widgets/account/status.dart';
|
||||||
@@ -121,210 +122,280 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
offset: Offset(1.0, 1.0),
|
offset: Offset(1.0, 1.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Widget publisherBasisWidget(SnPublisher data) => Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: Badge(
|
||||||
|
isLabelVisible: data.type == 0,
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
label: Icon(
|
||||||
|
Symbols.launch,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
offset: Offset(0, 48),
|
||||||
|
child: ProfilePictureWidget(file: data.picture, radius: 32),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
if (data.account?.name != null) {
|
||||||
|
context.pushNamed(
|
||||||
|
'accountProfile',
|
||||||
|
pathParameters: {'name': data.account!.name},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Text(data.nick).fontSize(20),
|
||||||
|
if (data.verification != null)
|
||||||
|
VerificationMark(mark: data.verification!),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'@${data.name}',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).fontSize(14).opacity(0.85),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.type == 0 && data.account != null)
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
data.type == 0 ? Symbols.person : Symbols.workspaces,
|
||||||
|
fill: 1,
|
||||||
|
size: 17,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'publisherBelongsTo'.tr(args: ['@${data.account!.name}']),
|
||||||
|
).fontSize(14),
|
||||||
|
],
|
||||||
|
).opacity(0.85).padding(bottom: 6),
|
||||||
|
if (data.type == 0 && data.account != null)
|
||||||
|
AccountStatusWidget(
|
||||||
|
uname: data.account!.name,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
subStatus
|
||||||
|
.when(
|
||||||
|
data:
|
||||||
|
(status) => FilledButton.icon(
|
||||||
|
onPressed:
|
||||||
|
subscribing.value
|
||||||
|
? null
|
||||||
|
: (status.isSubscribed
|
||||||
|
? unsubscribe
|
||||||
|
: subscribe),
|
||||||
|
icon: Icon(
|
||||||
|
status.isSubscribed
|
||||||
|
? Symbols.remove_circle
|
||||||
|
: Symbols.add_circle,
|
||||||
|
),
|
||||||
|
label:
|
||||||
|
Text(
|
||||||
|
status.isSubscribed
|
||||||
|
? 'unsubscribe'
|
||||||
|
: 'subscribe',
|
||||||
|
).tr(),
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity(vertical: -2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error: (_, _) => const SizedBox(),
|
||||||
|
loading:
|
||||||
|
() => const SizedBox(
|
||||||
|
height: 36,
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.padding(top: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, top: 24);
|
||||||
|
|
||||||
|
Widget publisherVerificationWidget(SnPublisher data) => Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (badges.value?.isNotEmpty ?? false)
|
||||||
|
BadgeList(badges: badges.value!).padding(top: 16),
|
||||||
|
if (data.verification != null)
|
||||||
|
VerificationStatusCard(mark: data.verification!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).padding(top: 16);
|
||||||
|
|
||||||
|
Widget publisherDetailWidget(SnPublisher data) => Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text('bio').tr().bold().padding(bottom: 2),
|
||||||
|
Text(data.bio.isEmpty ? 'descriptionNone'.tr() : data.bio),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, vertical: 16),
|
||||||
|
);
|
||||||
|
|
||||||
return publisher.when(
|
return publisher.when(
|
||||||
data:
|
data:
|
||||||
(data) => AppScaffold(
|
(data) => AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
body: CustomScrollView(
|
appBar:
|
||||||
slivers: [
|
isWideScreen(context)
|
||||||
SliverAppBar(
|
? AppBar(
|
||||||
foregroundColor: appbarColor.value,
|
foregroundColor: appbarColor.value,
|
||||||
expandedHeight: 180,
|
leading: PageBackButton(
|
||||||
pinned: true,
|
color: appbarColor.value,
|
||||||
leading: PageBackButton(
|
shadows: [appbarShadow],
|
||||||
color: appbarColor.value,
|
|
||||||
shadows: [appbarShadow],
|
|
||||||
),
|
|
||||||
flexibleSpace: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child:
|
|
||||||
data.background?.id != null
|
|
||||||
? CloudImageWidget(file: data.background)
|
|
||||||
: Container(
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).appBarTheme.backgroundColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
FlexibleSpaceBar(
|
flexibleSpace: Stack(
|
||||||
title: Text(
|
children: [
|
||||||
data.nick,
|
Positioned.fill(
|
||||||
style: TextStyle(
|
child:
|
||||||
color:
|
data.background?.id != null
|
||||||
appbarColor.value ??
|
? CloudImageWidget(file: data.background)
|
||||||
Theme.of(context).appBarTheme.foregroundColor,
|
: Container(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).appBarTheme.backgroundColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FlexibleSpaceBar(
|
||||||
|
title: Text(
|
||||||
|
data.nick,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
appbarColor.value ??
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).appBarTheme.foregroundColor,
|
||||||
|
shadows: [appbarShadow],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
background:
|
||||||
|
Container(), // Empty container since background is handled by Stack
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
body:
|
||||||
|
isWideScreen(context)
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
flex: 4,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverGap(16),
|
||||||
|
SliverPostList(pubName: name),
|
||||||
|
SliverGap(
|
||||||
|
MediaQuery.of(context).padding.bottom + 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(left: 8),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
flex: 3,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
publisherBasisWidget(data),
|
||||||
|
publisherVerificationWidget(data),
|
||||||
|
publisherDetailWidget(data),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
foregroundColor: appbarColor.value,
|
||||||
|
expandedHeight: 180,
|
||||||
|
pinned: true,
|
||||||
|
leading: PageBackButton(
|
||||||
|
color: appbarColor.value,
|
||||||
shadows: [appbarShadow],
|
shadows: [appbarShadow],
|
||||||
),
|
),
|
||||||
),
|
flexibleSpace: Stack(
|
||||||
background:
|
children: [
|
||||||
Container(), // Empty container since background is handled by Stack
|
Positioned.fill(
|
||||||
),
|
child:
|
||||||
],
|
data.background?.id != null
|
||||||
),
|
? CloudImageWidget(
|
||||||
),
|
file: data.background,
|
||||||
SliverToBoxAdapter(
|
)
|
||||||
child: Row(
|
: Container(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
color:
|
||||||
spacing: 20,
|
Theme.of(
|
||||||
children: [
|
context,
|
||||||
GestureDetector(
|
).appBarTheme.backgroundColor,
|
||||||
child: Badge(
|
),
|
||||||
isLabelVisible: data.type == 0,
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
label: Icon(
|
|
||||||
Symbols.launch,
|
|
||||||
size: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
offset: Offset(0, 48),
|
|
||||||
child: ProfilePictureWidget(
|
|
||||||
file: data.picture,
|
|
||||||
radius: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
if (data.account?.name != null) {
|
|
||||||
context.pushNamed(
|
|
||||||
'accountProfile',
|
|
||||||
pathParameters: {'name': data.account!.name},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Text(data.nick).fontSize(20),
|
|
||||||
if (data.verification != null)
|
|
||||||
VerificationMark(mark: data.verification!),
|
|
||||||
Text(
|
|
||||||
'@${data.name}',
|
|
||||||
).fontSize(14).opacity(0.85),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (data.type == 0 && data.account != null)
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
spacing: 6,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
data.type == 0
|
|
||||||
? Symbols.person
|
|
||||||
: Symbols.workspaces,
|
|
||||||
fill: 1,
|
|
||||||
size: 17,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'publisherBelongsTo'.tr(
|
|
||||||
args: ['@${data.account!.name}'],
|
|
||||||
),
|
|
||||||
).fontSize(14),
|
|
||||||
],
|
|
||||||
).opacity(0.85).padding(bottom: 6),
|
|
||||||
if (data.type == 0 && data.account != null)
|
|
||||||
AccountStatusWidget(
|
|
||||||
uname: data.account!.name,
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
),
|
||||||
subStatus
|
FlexibleSpaceBar(
|
||||||
.when(
|
title: Text(
|
||||||
data:
|
data.nick,
|
||||||
(status) => FilledButton.icon(
|
style: TextStyle(
|
||||||
onPressed:
|
color:
|
||||||
subscribing.value
|
appbarColor.value ??
|
||||||
? null
|
Theme.of(
|
||||||
: (status.isSubscribed
|
context,
|
||||||
? unsubscribe
|
).appBarTheme.foregroundColor,
|
||||||
: subscribe),
|
shadows: [appbarShadow],
|
||||||
icon: Icon(
|
),
|
||||||
status.isSubscribed
|
),
|
||||||
? Symbols.remove_circle
|
background:
|
||||||
: Symbols.add_circle,
|
Container(), // Empty container since background is handled by Stack
|
||||||
),
|
),
|
||||||
label:
|
],
|
||||||
Text(
|
),
|
||||||
status.isSubscribed
|
|
||||||
? 'unsubscribe'
|
|
||||||
: 'subscribe',
|
|
||||||
).tr(),
|
|
||||||
style: ButtonStyle(
|
|
||||||
visualDensity: VisualDensity(
|
|
||||||
vertical: -2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
error: (_, _) => const SizedBox(),
|
|
||||||
loading:
|
|
||||||
() => const SizedBox(
|
|
||||||
height: 36,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.padding(top: 8),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
SliverToBoxAdapter(child: publisherBasisWidget(data)),
|
||||||
],
|
SliverToBoxAdapter(
|
||||||
).padding(horizontal: 24, top: 24),
|
child: publisherVerificationWidget(data),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(child: publisherDetailWidget(data)),
|
||||||
child: Column(
|
SliverPostList(pubName: name),
|
||||||
children: [
|
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
if (badges.value?.isNotEmpty ?? false)
|
],
|
||||||
BadgeList(badges: badges.value!).padding(top: 16),
|
),
|
||||||
if (data.verification != null)
|
|
||||||
VerificationStatusCard(
|
|
||||||
mark: data.verification!,
|
|
||||||
).padding(top: 16),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: const Divider(height: 1).padding(vertical: 24),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text('bio').tr().bold(),
|
|
||||||
Text(
|
|
||||||
data.bio.isEmpty ? 'descriptionNone'.tr() : data.bio,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: const Divider(height: 1).padding(top: 24),
|
|
||||||
),
|
|
||||||
SliverPostList(pubName: name),
|
|
||||||
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
error:
|
error:
|
||||||
(error, stackTrace) => AppScaffold(
|
(error, stackTrace) => AppScaffold(
|
||||||
|
isNoBackground: false,
|
||||||
appBar: AppBar(leading: const PageBackButton()),
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
body: Center(child: Text(error.toString())),
|
body: Center(child: Text(error.toString())),
|
||||||
),
|
),
|
||||||
loading:
|
loading:
|
||||||
() => AppScaffold(
|
() => AppScaffold(
|
||||||
|
isNoBackground: false,
|
||||||
appBar: AppBar(leading: const PageBackButton()),
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
body: Center(child: CircularProgressIndicator()),
|
body: Center(child: CircularProgressIndicator()),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class RealmDetailScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
body: realmState.when(
|
body: realmState.when(
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (error, _) => Center(child: Text('Error: $error')),
|
error: (error, _) => Center(child: Text('Error: $error')),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class RealmListScreen extends HookConsumerWidget {
|
|||||||
final realmInvites = ref.watch(realmInvitesProvider);
|
final realmInvites = ref.watch(realmInvitesProvider);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('realms').tr(),
|
title: const Text('realms').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -279,7 +279,7 @@ class EditRealmScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()),
|
title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()),
|
||||||
leading: const PageBackButton(),
|
leading: const PageBackButton(),
|
||||||
|
|||||||
@@ -552,7 +552,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('settings').tr(),
|
title: Text('settings').tr(),
|
||||||
actions:
|
actions:
|
||||||
|
|||||||
@@ -140,30 +140,27 @@ class VerificationStatusCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Column(
|
||||||
margin: EdgeInsets.zero,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Icon(
|
||||||
children: [
|
mark.type == 4
|
||||||
Icon(
|
? Symbols.play_circle
|
||||||
mark.type == 4
|
: mark.type == 0
|
||||||
? Symbols.play_circle
|
? Symbols.build_circle
|
||||||
: mark.type == 0
|
: Symbols.verified,
|
||||||
? Symbols.build_circle
|
size: 32,
|
||||||
: Symbols.verified,
|
color: kVerificationMarkColors[mark.type],
|
||||||
size: 32,
|
fill: 1,
|
||||||
color: kVerificationMarkColors[mark.type],
|
),
|
||||||
fill: 1,
|
const Gap(8),
|
||||||
),
|
Text(mark.title ?? 'No title').bold(),
|
||||||
const Gap(8),
|
Text(mark.description ?? 'descriptionNone'.tr()),
|
||||||
Text(mark.title ?? 'No title').bold(),
|
const Gap(6),
|
||||||
Text(mark.description ?? 'descriptionNone'.tr()),
|
Text(
|
||||||
const Gap(6),
|
'Verified by\n${mark.verifiedBy ?? 'No one verified it'}',
|
||||||
Text(
|
).fontSize(11).opacity(0.8),
|
||||||
'Verified by\n${mark.verifiedBy ?? 'No one verified it'}',
|
],
|
||||||
).fontSize(11).opacity(0.8),
|
).padding(horizontal: 24, vertical: 16);
|
||||||
],
|
|
||||||
).padding(horizontal: 24, vertical: 16),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ Future<void> showAccountProfileCard(
|
|||||||
offset: offset ?? Offset.zero,
|
offset: offset ?? Offset.zero,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AccountProfileCard(uname: uname),
|
builder: (context) => AccountProfileCard(uname: uname),
|
||||||
|
alignment: Alignment.center,
|
||||||
dimBackground: true,
|
dimBackground: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class FortuneGraphWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
final String? eventCalanderUser;
|
final String? eventCalanderUser;
|
||||||
|
|
||||||
|
final EdgeInsets? margin;
|
||||||
|
|
||||||
const FortuneGraphWidget({
|
const FortuneGraphWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.events,
|
required this.events,
|
||||||
@@ -34,6 +36,7 @@ class FortuneGraphWidget extends HookConsumerWidget {
|
|||||||
this.height = 180,
|
this.height = 180,
|
||||||
this.onPointSelected,
|
this.onPointSelected,
|
||||||
this.eventCalanderUser,
|
this.eventCalanderUser,
|
||||||
|
this.margin,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -249,7 +252,7 @@ class FortuneGraphWidget extends HookConsumerWidget {
|
|||||||
if (constrainWidth) {
|
if (constrainWidth) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
child: Card(margin: EdgeInsets.all(16), child: content),
|
child: Card(margin: margin ?? EdgeInsets.all(16), child: content),
|
||||||
).center();
|
).center();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class AccountStatusCreationWidget extends HookConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => AccountStatusCreationSheet(
|
(context) => AccountStatusCreationSheet(
|
||||||
initialStatus:
|
initialStatus:
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class AccountStatusCreationSheet extends HookConsumerWidget {
|
|||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
final apiClient = ref.read(apiClientProvider);
|
final apiClient = ref.read(apiClientProvider);
|
||||||
await apiClient.request(
|
await apiClient.request(
|
||||||
'/accounts/me/statuses',
|
'/id/accounts/me/statuses',
|
||||||
data: {
|
data: {
|
||||||
'attitude': attitude.value,
|
'attitude': attitude.value,
|
||||||
'is_invisible': isInvisible.value,
|
'is_invisible': isInvisible.value,
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class AppScaffold extends StatelessWidget {
|
|||||||
final AppBar? appBar;
|
final AppBar? appBar;
|
||||||
final DrawerCallback? onDrawerChanged;
|
final DrawerCallback? onDrawerChanged;
|
||||||
final DrawerCallback? onEndDrawerChanged;
|
final DrawerCallback? onEndDrawerChanged;
|
||||||
final bool? noBackground;
|
final bool? isNoBackground;
|
||||||
final bool? extendBody;
|
final bool? extendBody;
|
||||||
|
|
||||||
const AppScaffold({
|
const AppScaffold({
|
||||||
@@ -181,7 +181,7 @@ class AppScaffold extends StatelessWidget {
|
|||||||
this.endDrawer,
|
this.endDrawer,
|
||||||
this.onDrawerChanged,
|
this.onDrawerChanged,
|
||||||
this.onEndDrawerChanged,
|
this.onEndDrawerChanged,
|
||||||
this.noBackground,
|
this.isNoBackground,
|
||||||
this.extendBody,
|
this.extendBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ class AppScaffold extends StatelessWidget {
|
|||||||
final appBarHeight = appBar?.preferredSize.height ?? 0;
|
final appBarHeight = appBar?.preferredSize.height ?? 0;
|
||||||
final safeTop = MediaQuery.of(context).padding.top;
|
final safeTop = MediaQuery.of(context).padding.top;
|
||||||
|
|
||||||
final noBackground = this.noBackground ?? isWideScreen(context);
|
final noBackground = isNoBackground ?? isWideScreen(context);
|
||||||
|
|
||||||
final content = Column(
|
final content = Column(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/chat/call_participant_tile.dart';
|
import 'package:island/widgets/chat/call_participant_tile.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
|
||||||
@@ -55,7 +57,49 @@ class CallControlsBar extends HookConsumerWidget {
|
|||||||
const Gap(16),
|
const Gap(16),
|
||||||
_buildCircularButton(
|
_buildCircularButton(
|
||||||
icon: Icons.call_end,
|
icon: Icons.call_end,
|
||||||
onPressed: () => callNotifier.disconnect(),
|
onPressed:
|
||||||
|
() => showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useRootNavigator: true,
|
||||||
|
builder:
|
||||||
|
(context) => ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.logout, fill: 1),
|
||||||
|
title: Text('callLeave').tr(),
|
||||||
|
onTap: () {
|
||||||
|
callNotifier.disconnect();
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.call_end, fill: 1),
|
||||||
|
iconColor: Colors.red,
|
||||||
|
title: Text('callEnd').tr(),
|
||||||
|
onTap: () async {
|
||||||
|
callNotifier.disconnect();
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
await apiClient.delete(
|
||||||
|
'/sphere/chat/realtime/${callNotifier.roomId}',
|
||||||
|
);
|
||||||
|
callNotifier.dispose();
|
||||||
|
if (context.mounted) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
backgroundColor: const Color(0xFFE53E3E),
|
backgroundColor: const Color(0xFFE53E3E),
|
||||||
iconColor: Colors.white,
|
iconColor: Colors.white,
|
||||||
),
|
),
|
||||||
@@ -279,7 +323,7 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -294,17 +338,7 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
height: 40,
|
height: 40,
|
||||||
child:
|
child:
|
||||||
SpeakingRippleAvatar(
|
SpeakingRippleAvatar(
|
||||||
isSpeaking: lastSpeaker.isSpeaking,
|
live: lastSpeaker,
|
||||||
audioLevel:
|
|
||||||
lastSpeaker.remoteParticipant.audioLevel,
|
|
||||||
pictureId:
|
|
||||||
lastSpeaker
|
|
||||||
.participant
|
|
||||||
.profile
|
|
||||||
?.account
|
|
||||||
.profile
|
|
||||||
.picture
|
|
||||||
?.id,
|
|
||||||
size: 36,
|
size: 36,
|
||||||
).center(),
|
).center(),
|
||||||
);
|
);
|
||||||
@@ -314,10 +348,7 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text('@${lastSpeaker.participant.identity}').bold(),
|
||||||
lastSpeaker.participant.profile?.account.nick ??
|
|
||||||
'unknown'.tr(),
|
|
||||||
).bold(),
|
|
||||||
Text(
|
Text(
|
||||||
formatDuration(callState.duration),
|
formatDuration(callState.duration),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
@@ -360,7 +391,10 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
).padding(all: 16),
|
).padding(all: 16),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('chatCall', pathParameters: {'id': callNotifier.roomId!});
|
context.pushNamed(
|
||||||
|
'chatCall',
|
||||||
|
pathParameters: {'id': callNotifier.roomId!},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
94
lib/widgets/chat/call_participant_card.dart
Normal file
94
lib/widgets/chat/call_participant_card.dart
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_popup_card/flutter_popup_card.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/call.dart';
|
||||||
|
import 'package:island/widgets/account/account_nameplate.dart';
|
||||||
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class CallParticipantCard extends HookConsumerWidget {
|
||||||
|
final CallParticipantLive live;
|
||||||
|
const CallParticipantCard({super.key, required this.live});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final width =
|
||||||
|
math.min(MediaQuery.of(context).size.width - 80, 360).toDouble();
|
||||||
|
return PopupCard(
|
||||||
|
elevation: 8,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.wifi, size: 16),
|
||||||
|
const Gap(8),
|
||||||
|
Text(switch (live.remoteParticipant.connectionQuality) {
|
||||||
|
ConnectionQuality.excellent => 'Excellent',
|
||||||
|
ConnectionQuality.good => 'Good',
|
||||||
|
ConnectionQuality.poor => 'Bad',
|
||||||
|
ConnectionQuality.lost => 'Lost',
|
||||||
|
_ => 'Connecting',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16),
|
||||||
|
AccountNameplate(
|
||||||
|
name: live.participant.identity,
|
||||||
|
isOutlined: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CallParticipantGestureDetector extends StatelessWidget {
|
||||||
|
final CallParticipantLive participant;
|
||||||
|
final Widget child;
|
||||||
|
const CallParticipantGestureDetector({
|
||||||
|
super.key,
|
||||||
|
required this.participant,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
child: child,
|
||||||
|
onTapDown: (details) {
|
||||||
|
showCallParticipantCard(
|
||||||
|
context,
|
||||||
|
participant,
|
||||||
|
offset: details.localPosition,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showCallParticipantCard(
|
||||||
|
BuildContext context,
|
||||||
|
CallParticipantLive participant, {
|
||||||
|
Offset? offset,
|
||||||
|
}) async {
|
||||||
|
await showPopupCard<void>(
|
||||||
|
offset: offset ?? Offset.zero,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CallParticipantCard(live: participant),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
dimBackground: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,92 +1,127 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
|
import 'package:island/screens/account/profile.dart';
|
||||||
|
import 'package:island/widgets/chat/call_participant_card.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:livekit_client/livekit_client.dart';
|
import 'package:livekit_client/livekit_client.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
class SpeakingRippleAvatar extends StatelessWidget {
|
class SpeakingRippleAvatar extends HookConsumerWidget {
|
||||||
final bool isSpeaking;
|
final CallParticipantLive live;
|
||||||
final double audioLevel;
|
|
||||||
final String? pictureId;
|
|
||||||
final double size;
|
final double size;
|
||||||
|
|
||||||
const SpeakingRippleAvatar({
|
const SpeakingRippleAvatar({super.key, required this.live, this.size = 96});
|
||||||
super.key,
|
|
||||||
required this.isSpeaking,
|
|
||||||
required this.audioLevel,
|
|
||||||
required this.pictureId,
|
|
||||||
this.size = 96,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final account = ref.watch(accountProvider(live.participant.identity));
|
||||||
|
|
||||||
final avatarRadius = size / 2;
|
final avatarRadius = size / 2;
|
||||||
final clampedLevel = audioLevel.clamp(0.0, 1.0);
|
final clampedLevel = live.remoteParticipant.audioLevel.clamp(0.0, 1.0);
|
||||||
final rippleRadius = avatarRadius + clampedLevel * (size * 0.333);
|
final rippleRadius = avatarRadius + clampedLevel * (size * 0.333);
|
||||||
return TweenAnimationBuilder<double>(
|
return SizedBox(
|
||||||
tween: Tween<double>(
|
width: size + 8,
|
||||||
begin: avatarRadius,
|
height: size + 8,
|
||||||
end: isSpeaking ? rippleRadius : avatarRadius,
|
child: TweenAnimationBuilder<double>(
|
||||||
),
|
tween: Tween<double>(
|
||||||
duration: const Duration(milliseconds: 250),
|
begin: avatarRadius,
|
||||||
curve: Curves.easeOut,
|
end: live.remoteParticipant.isSpeaking ? rippleRadius : avatarRadius,
|
||||||
builder: (context, animatedRadius, child) {
|
),
|
||||||
return Stack(
|
duration: const Duration(milliseconds: 250),
|
||||||
alignment: Alignment.center,
|
curve: Curves.easeOut,
|
||||||
children: [
|
builder: (context, animatedRadius, child) {
|
||||||
if (isSpeaking)
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
if (live.remoteParticipant.isSpeaking)
|
||||||
|
Container(
|
||||||
|
width: animatedRadius * 2,
|
||||||
|
height: animatedRadius * 2,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.green.withOpacity(0.75 + 0.25 * clampedLevel),
|
||||||
|
),
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
width: animatedRadius * 2,
|
width: size,
|
||||||
height: animatedRadius * 2,
|
height: size,
|
||||||
decoration: BoxDecoration(
|
alignment: Alignment.center,
|
||||||
shape: BoxShape.circle,
|
decoration: BoxDecoration(shape: BoxShape.circle),
|
||||||
color: Colors.green.withOpacity(0.75 + 0.25 * clampedLevel),
|
child: account.when(
|
||||||
|
data:
|
||||||
|
(value) => CallParticipantGestureDetector(
|
||||||
|
participant: live,
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: value.profile.picture,
|
||||||
|
radius: size / 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
error:
|
||||||
|
(_, _) => CircleAvatar(
|
||||||
|
radius: size / 2,
|
||||||
|
child: const Icon(Symbols.person_remove),
|
||||||
|
),
|
||||||
|
loading:
|
||||||
|
() => CircleAvatar(
|
||||||
|
radius: size / 2,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
if (live.remoteParticipant.isMuted)
|
||||||
width: size,
|
Positioned(
|
||||||
height: size,
|
bottom: 4,
|
||||||
alignment: Alignment.center,
|
right: 4,
|
||||||
decoration: BoxDecoration(shape: BoxShape.circle),
|
child: Container(
|
||||||
child: ProfilePictureWidget(fileId: pictureId, radius: size / 2),
|
width: 20,
|
||||||
),
|
height: 20,
|
||||||
],
|
decoration: BoxDecoration(
|
||||||
);
|
color: Colors.red,
|
||||||
},
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Symbols.mic_off,
|
||||||
|
size: 14,
|
||||||
|
fill: 1,
|
||||||
|
).padding(left: 1.5, top: 1.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CallParticipantTile extends StatelessWidget {
|
class CallParticipantTile extends HookConsumerWidget {
|
||||||
final CallParticipantLive live;
|
final CallParticipantLive live;
|
||||||
|
|
||||||
const CallParticipantTile({super.key, required this.live});
|
const CallParticipantTile({super.key, required this.live});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final hasVideo =
|
final hasVideo =
|
||||||
live.hasVideo &&
|
live.hasVideo &&
|
||||||
live.remoteParticipant.trackPublications.values
|
live.remoteParticipant.trackPublications.values
|
||||||
.where((pub) => pub.track != null && pub.kind == TrackType.VIDEO)
|
.where((pub) => pub.track != null && pub.kind == TrackType.VIDEO)
|
||||||
.isNotEmpty;
|
.isNotEmpty;
|
||||||
final audioLevel = live.remoteParticipant.audioLevel;
|
|
||||||
|
|
||||||
if (hasVideo) {
|
if (hasVideo) {
|
||||||
return Stack(
|
return Stack(
|
||||||
fit: StackFit.loose,
|
fit: StackFit.loose,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
AspectRatio(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
aspectRatio: 16 / 9,
|
||||||
child: AspectRatio(
|
child: VideoTrackRenderer(
|
||||||
aspectRatio: 16 / 9,
|
live.remoteParticipant.trackPublications.values
|
||||||
child: VideoTrackRenderer(
|
.where((track) => track.kind == TrackType.VIDEO)
|
||||||
live.remoteParticipant.trackPublications.values
|
.first
|
||||||
.where((track) => track.kind == TrackType.VIDEO)
|
.track
|
||||||
.first
|
as VideoTrack,
|
||||||
.track
|
renderMode: VideoRenderMode.platformView,
|
||||||
as VideoTrack,
|
|
||||||
renderMode: VideoRenderMode.platformView,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -94,21 +129,26 @@ class CallParticipantTile extends StatelessWidget {
|
|||||||
right: 8,
|
right: 8,
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
child: Text(
|
child: Text(
|
||||||
live.participant.profile?.account.nick ??
|
'@${live.participant.name}',
|
||||||
'${'unknown'.tr()}\'s video',
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(fontSize: 14, color: Colors.white),
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
spreadRadius: 8,
|
||||||
|
blurRadius: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return SpeakingRippleAvatar(
|
return SpeakingRippleAvatar(size: 84, live: live);
|
||||||
isSpeaking: live.isSpeaking,
|
|
||||||
audioLevel: audioLevel,
|
|
||||||
pictureId: live.participant.profile?.account.profile.picture?.id,
|
|
||||||
size: 84,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,19 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/database/message.dart';
|
import 'package:island/database/message.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/models/embed.dart';
|
import 'package:island/models/embed.dart';
|
||||||
import 'package:island/pods/call.dart';
|
import 'package:island/pods/call.dart';
|
||||||
|
import 'package:island/pods/translate.dart';
|
||||||
import 'package:island/screens/chat/room.dart';
|
import 'package:island/screens/chat/room.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/account/account_pfc.dart';
|
import 'package:island/widgets/account/account_pfc.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/alert.native.dart';
|
||||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/embed/link.dart';
|
import 'package:island/widgets/content/embed/link.dart';
|
||||||
@@ -67,6 +70,46 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
|
|
||||||
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
||||||
|
|
||||||
|
final messageLanguage =
|
||||||
|
remoteMessage.content != null
|
||||||
|
? ref.watch(detectStringLanguageProvider(remoteMessage.content!))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
final currentLanguage = context.locale.toString();
|
||||||
|
final translatableLanguage =
|
||||||
|
messageLanguage != null
|
||||||
|
? messageLanguage.substring(0, 2) != currentLanguage.substring(0, 2)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
final translating = useState(false);
|
||||||
|
final translatedText = useState<String?>(null);
|
||||||
|
|
||||||
|
Future<void> translate() async {
|
||||||
|
if (translatedText.value != null) {
|
||||||
|
translatedText.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translating.value) return;
|
||||||
|
if (remoteMessage.content == null) return;
|
||||||
|
translating.value = true;
|
||||||
|
try {
|
||||||
|
final text = await ref.watch(
|
||||||
|
translateStringProvider(
|
||||||
|
TranslateQuery(
|
||||||
|
text: remoteMessage.content!,
|
||||||
|
lang: currentLanguage.substring(0, 2),
|
||||||
|
),
|
||||||
|
).future,
|
||||||
|
);
|
||||||
|
translatedText.value = text;
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
translating.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ContextMenuWidget(
|
return ContextMenuWidget(
|
||||||
menuProvider: (_) {
|
menuProvider: (_) {
|
||||||
if (onAction == null) return Menu(children: []);
|
if (onAction == null) return Menu(children: []);
|
||||||
@@ -103,6 +146,18 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
onAction!.call(MessageItemAction.forward);
|
onAction!.call(MessageItemAction.forward);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (translatableLanguage) MenuSeparator(),
|
||||||
|
if (translatableLanguage)
|
||||||
|
MenuAction(
|
||||||
|
title:
|
||||||
|
translatedText.value == null
|
||||||
|
? 'translate'.tr()
|
||||||
|
: translating.value
|
||||||
|
? 'translating'.tr()
|
||||||
|
: 'translated'.tr(),
|
||||||
|
image: MenuImage.icon(Symbols.translate),
|
||||||
|
callback: translate,
|
||||||
|
),
|
||||||
if (isMobile) MenuSeparator(),
|
if (isMobile) MenuSeparator(),
|
||||||
if (isMobile)
|
if (isMobile)
|
||||||
MenuAction(
|
MenuAction(
|
||||||
@@ -221,14 +276,18 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
isReply: false,
|
isReply: false,
|
||||||
).padding(vertical: 4),
|
).padding(vertical: 4),
|
||||||
if (_MessageItemContent.hasContent(remoteMessage))
|
if (_MessageItemContent.hasContent(remoteMessage))
|
||||||
_MessageItemContent(item: remoteMessage),
|
_MessageItemContent(
|
||||||
|
item: remoteMessage,
|
||||||
|
translatedText: translatedText.value,
|
||||||
|
),
|
||||||
if (remoteMessage.attachments.isNotEmpty)
|
if (remoteMessage.attachments.isNotEmpty)
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return CloudFileList(
|
return CloudFileList(
|
||||||
files: remoteMessage.attachments,
|
files: remoteMessage.attachments,
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
).padding(vertical: 4);
|
padding: EdgeInsets.symmetric(vertical: 4),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (remoteMessage.meta['embeds'] != null)
|
if (remoteMessage.meta['embeds'] != null)
|
||||||
@@ -481,7 +540,8 @@ class MessageQuoteWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
class _MessageItemContent extends StatelessWidget {
|
class _MessageItemContent extends StatelessWidget {
|
||||||
final SnChatMessage item;
|
final SnChatMessage item;
|
||||||
const _MessageItemContent({required this.item});
|
final String? translatedText;
|
||||||
|
const _MessageItemContent({required this.item, this.translatedText});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -494,10 +554,40 @@ class _MessageItemContent extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
case 'text':
|
case 'text':
|
||||||
default:
|
default:
|
||||||
return MarkdownTextContent(
|
return Column(
|
||||||
content: item.content!,
|
mainAxisSize: MainAxisSize.min,
|
||||||
isSelectable: true,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
linesMargin: EdgeInsets.zero,
|
children: [
|
||||||
|
MarkdownTextContent(
|
||||||
|
content: item.content!,
|
||||||
|
isSelectable: true,
|
||||||
|
linesMargin: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
if (translatedText?.isNotEmpty ?? false)
|
||||||
|
...([
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: math.min(
|
||||||
|
280,
|
||||||
|
MediaQuery.of(context).size.width * 0.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('translated').tr().fontSize(11).opacity(0.75),
|
||||||
|
const Gap(8),
|
||||||
|
Flexible(child: Divider()),
|
||||||
|
],
|
||||||
|
).padding(vertical: 4),
|
||||||
|
),
|
||||||
|
MarkdownTextContent(
|
||||||
|
content: translatedText!,
|
||||||
|
isSelectable: true,
|
||||||
|
linesMargin: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ Future<SnCheckInResult?> checkInResultToday(Ref ref) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CheckInWidget extends HookConsumerWidget {
|
class CheckInWidget extends HookConsumerWidget {
|
||||||
const CheckInWidget({super.key});
|
final EdgeInsets? margin;
|
||||||
|
const CheckInWidget({super.key, this.margin});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -66,7 +67,8 @@ class CheckInWidget extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8),
|
margin:
|
||||||
|
margin ?? EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:island/pods/network.dart';
|
|||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:path/path.dart' show extension;
|
import 'package:path/path.dart' show extension;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
@@ -27,14 +28,16 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
final double? minWidth;
|
final double? minWidth;
|
||||||
final bool disableZoomIn;
|
final bool disableZoomIn;
|
||||||
final bool disableConstraint;
|
final bool disableConstraint;
|
||||||
|
final EdgeInsets? padding;
|
||||||
const CloudFileList({
|
const CloudFileList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.files,
|
required this.files,
|
||||||
this.maxHeight = 360,
|
this.maxHeight = 560,
|
||||||
this.maxWidth = double.infinity,
|
this.maxWidth = double.infinity,
|
||||||
this.minWidth,
|
this.minWidth,
|
||||||
this.disableZoomIn = false,
|
this.disableZoomIn = false,
|
||||||
this.disableConstraint = false,
|
this.disableConstraint = false,
|
||||||
|
this.padding,
|
||||||
});
|
});
|
||||||
|
|
||||||
double calculateAspectRatio() {
|
double calculateAspectRatio() {
|
||||||
@@ -60,22 +63,17 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
if (files.isEmpty) return const SizedBox.shrink();
|
if (files.isEmpty) return const SizedBox.shrink();
|
||||||
if (files.length == 1) {
|
if (files.length == 1) {
|
||||||
final isImage = files.first.mimeType?.startsWith('image') ?? false;
|
final isImage = files.first.mimeType?.startsWith('image') ?? false;
|
||||||
return ConstrainedBox(
|
return Container(
|
||||||
|
padding: padding,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxHeight: disableConstraint ? double.infinity : maxHeight,
|
maxHeight: disableConstraint ? double.infinity : maxHeight,
|
||||||
minWidth: minWidth ?? 0,
|
minWidth: minWidth ?? 0,
|
||||||
maxWidth:
|
maxWidth: files.length == 1 ? maxWidth : double.infinity,
|
||||||
files.length == 1
|
|
||||||
? math.max(
|
|
||||||
math.min(520, MediaQuery.of(context).size.width * 0.85),
|
|
||||||
minWidth ?? 0,
|
|
||||||
)
|
|
||||||
: double.infinity,
|
|
||||||
),
|
),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: calculateAspectRatio(),
|
aspectRatio: calculateAspectRatio(),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: _CloudFileListEntry(
|
child: _CloudFileListEntry(
|
||||||
file: files.first,
|
file: files.first,
|
||||||
heroTag: heroTags.first,
|
heroTag: heroTags.first,
|
||||||
@@ -95,7 +93,7 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).padding(horizontal: 3);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final allImages =
|
final allImages =
|
||||||
@@ -109,22 +107,37 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: calculateAspectRatio(),
|
aspectRatio: calculateAspectRatio(),
|
||||||
child: CarouselView(
|
child: CarouselView(
|
||||||
|
padding: padding,
|
||||||
|
itemSnapping: true,
|
||||||
itemExtent: math.min(
|
itemExtent: math.min(
|
||||||
MediaQuery.of(context).size.width * 0.85,
|
MediaQuery.of(context).size.width * 0.85,
|
||||||
maxWidth * 0.85,
|
maxWidth * 0.85,
|
||||||
),
|
),
|
||||||
itemSnapping: true,
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
for (var i = 0; i < files.length; i++)
|
for (var i = 0; i < files.length; i++)
|
||||||
_CloudFileListEntry(
|
Stack(
|
||||||
file: files[i],
|
children: [
|
||||||
heroTag: heroTags[i],
|
_CloudFileListEntry(
|
||||||
isImage: files[i].mimeType?.startsWith('image') ?? false,
|
file: files[i],
|
||||||
disableZoomIn: disableZoomIn,
|
heroTag: heroTags[i],
|
||||||
fit: BoxFit.cover,
|
isImage: files[i].mimeType?.startsWith('image') ?? false,
|
||||||
|
disableZoomIn: disableZoomIn,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 12,
|
||||||
|
left: 16,
|
||||||
|
child: Text('${i + 1}/${files.length}')
|
||||||
|
.textColor(Colors.white)
|
||||||
|
.textShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
blurRadius: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onTap: (i) {
|
onTap: (i) {
|
||||||
@@ -150,28 +163,52 @@ class CloudFileList extends HookConsumerWidget {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: files.length,
|
itemCount: files.length,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 3),
|
padding: padding,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return ClipRRect(
|
return AspectRatio(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
aspectRatio:
|
||||||
child: _CloudFileListEntry(
|
files[index].fileMeta?['ratio'] is num
|
||||||
file: files[index],
|
? files[index].fileMeta!['ratio'].toDouble()
|
||||||
heroTag: heroTags[index],
|
: 1.0,
|
||||||
isImage: files[index].mimeType?.startsWith('image') ?? false,
|
child: Stack(
|
||||||
disableZoomIn: disableZoomIn,
|
children: [
|
||||||
onTap: () {
|
ClipRRect(
|
||||||
if (!(files[index].mimeType?.startsWith('image') ?? false)) {
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
return;
|
child: _CloudFileListEntry(
|
||||||
}
|
file: files[index],
|
||||||
if (!disableZoomIn) {
|
heroTag: heroTags[index],
|
||||||
context.pushTransparentRoute(
|
isImage:
|
||||||
CloudFileZoomIn(
|
files[index].mimeType?.startsWith('image') ?? false,
|
||||||
item: files[index],
|
disableZoomIn: disableZoomIn,
|
||||||
heroTag: heroTags[index],
|
onTap: () {
|
||||||
),
|
if (!(files[index].mimeType?.startsWith('image') ??
|
||||||
);
|
false)) {
|
||||||
}
|
return;
|
||||||
},
|
}
|
||||||
|
if (!disableZoomIn) {
|
||||||
|
context.pushTransparentRoute(
|
||||||
|
CloudFileZoomIn(
|
||||||
|
item: files[index],
|
||||||
|
heroTag: heroTags[index],
|
||||||
|
),
|
||||||
|
rootNavigator: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 12,
|
||||||
|
left: 16,
|
||||||
|
child: Text('${index + 1}/${files.length}')
|
||||||
|
.textColor(Colors.white)
|
||||||
|
.textShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
blurRadius: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -193,6 +230,8 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
|||||||
final photoViewController = useMemoized(() => PhotoViewController(), []);
|
final photoViewController = useMemoized(() => PhotoViewController(), []);
|
||||||
final rotation = useState(0);
|
final rotation = useState(0);
|
||||||
|
|
||||||
|
final showOriginal = useState(false);
|
||||||
|
|
||||||
Future<void> saveToGallery() async {
|
Future<void> saveToGallery() async {
|
||||||
try {
|
try {
|
||||||
// Show loading indicator
|
// Show loading indicator
|
||||||
@@ -206,7 +245,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
|||||||
final filePath = '${tempDir.path}/${item.id}.${extension(item.name)}';
|
final filePath = '${tempDir.path}/${item.id}.${extension(item.name)}';
|
||||||
|
|
||||||
await client.download(
|
await client.download(
|
||||||
'/files/${item.id}',
|
'/drive/files/${item.id}',
|
||||||
filePath,
|
filePath,
|
||||||
queryParameters: {'original': true},
|
queryParameters: {'original': true},
|
||||||
);
|
);
|
||||||
@@ -356,7 +395,7 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
|||||||
imageProvider: CloudImageWidget.provider(
|
imageProvider: CloudImageWidget.provider(
|
||||||
fileId: item.id,
|
fileId: item.id,
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
original: true,
|
original: showOriginal.value,
|
||||||
),
|
),
|
||||||
// Apply rotation transformation
|
// Apply rotation transformation
|
||||||
customSize: MediaQuery.of(context).size,
|
customSize: MediaQuery.of(context).size,
|
||||||
@@ -390,6 +429,23 @@ class CloudFileZoomIn extends HookConsumerWidget {
|
|||||||
saveToGallery();
|
saveToGallery();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showOriginal.value = !showOriginal.value;
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
showOriginal.value ? Symbols.raw_on : Symbols.raw_off,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
blurRadius: 5.0,
|
||||||
|
offset: Offset(1.0, 1.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -497,7 +553,6 @@ class _CloudFileListEntry extends StatelessWidget {
|
|||||||
final bool isImage;
|
final bool isImage;
|
||||||
final bool disableZoomIn;
|
final bool disableZoomIn;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final BoxFit fit;
|
|
||||||
|
|
||||||
const _CloudFileListEntry({
|
const _CloudFileListEntry({
|
||||||
required this.file,
|
required this.file,
|
||||||
@@ -505,7 +560,6 @@ class _CloudFileListEntry extends StatelessWidget {
|
|||||||
required this.isImage,
|
required this.isImage,
|
||||||
required this.disableZoomIn,
|
required this.disableZoomIn,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.fit = BoxFit.contain,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -528,10 +582,10 @@ class _CloudFileListEntry extends StatelessWidget {
|
|||||||
item: file,
|
item: file,
|
||||||
heroTag: heroTag,
|
heroTag: heroTag,
|
||||||
noBlurhash: true,
|
noBlurhash: true,
|
||||||
fit: fit,
|
fit: BoxFit.contain,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
CloudFileWidget(item: file, heroTag: heroTag, fit: fit),
|
CloudFileWidget(item: file, heroTag: heroTag, fit: BoxFit.contain),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/services/time.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ class CloudFileWidget extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
"video" => AspectRatio(
|
"video" => AspectRatio(
|
||||||
aspectRatio: ratio,
|
aspectRatio: ratio,
|
||||||
child: UniversalVideo(uri: uri, aspectRatio: ratio),
|
child: CloudVideoWidget(item: item),
|
||||||
),
|
),
|
||||||
_ => Text('Unable render for ${item.mimeType}'),
|
_ => Text('Unable render for ${item.mimeType}'),
|
||||||
};
|
};
|
||||||
@@ -58,6 +61,119 @@ class CloudFileWidget extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CloudVideoWidget extends HookConsumerWidget {
|
||||||
|
final SnCloudFile item;
|
||||||
|
const CloudVideoWidget({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final open = useState(false);
|
||||||
|
|
||||||
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
|
final uri = '$serverUrl/drive/files/${item.id}';
|
||||||
|
|
||||||
|
var ratio =
|
||||||
|
item.fileMeta?['ratio'] is num
|
||||||
|
? item.fileMeta!['ratio'].toDouble()
|
||||||
|
: 1.0;
|
||||||
|
if (ratio == 0) ratio = 1.0;
|
||||||
|
|
||||||
|
if (open.value) {
|
||||||
|
return UniversalVideo(uri: uri, aspectRatio: ratio, autoplay: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
UniversalImage(uri: '$uri?thumbnail=true'),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Center(
|
||||||
|
child: const Icon(
|
||||||
|
Symbols.play_arrow,
|
||||||
|
fill: 1,
|
||||||
|
size: 32,
|
||||||
|
shadows: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
spreadRadius: 8,
|
||||||
|
blurRadius: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
if (item.fileMeta?['duration'] != null)
|
||||||
|
Text(
|
||||||
|
Duration(
|
||||||
|
milliseconds:
|
||||||
|
((item.fileMeta?['duration'] as num) * 1000)
|
||||||
|
.toInt(),
|
||||||
|
).formatDuration(),
|
||||||
|
style: TextStyle(
|
||||||
|
shadows: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
spreadRadius: 8,
|
||||||
|
blurRadius: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.fileMeta?['bit_rate'] != null)
|
||||||
|
Text(
|
||||||
|
'${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps',
|
||||||
|
style: TextStyle(
|
||||||
|
shadows: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
spreadRadius: 8,
|
||||||
|
blurRadius: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
shadows: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
offset: Offset(1, 1),
|
||||||
|
spreadRadius: 8,
|
||||||
|
blurRadius: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).padding(horizontal: 16, bottom: 12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
open.value = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CloudImageWidget extends ConsumerWidget {
|
class CloudImageWidget extends ConsumerWidget {
|
||||||
final String? fileId;
|
final String? fileId;
|
||||||
final SnCloudFile? file;
|
final SnCloudFile? file;
|
||||||
@@ -92,7 +208,10 @@ class CloudImageWidget extends ConsumerWidget {
|
|||||||
required String serverUrl,
|
required String serverUrl,
|
||||||
bool original = false,
|
bool original = false,
|
||||||
}) {
|
}) {
|
||||||
final uri = '$serverUrl/drive/files/$fileId?original=$original';
|
final uri =
|
||||||
|
original
|
||||||
|
? '$serverUrl/drive/files/$fileId?original=true'
|
||||||
|
: '$serverUrl/drive/files/$fileId';
|
||||||
return CachedNetworkImageProvider(uri);
|
return CachedNetworkImageProvider(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import 'package:media_kit_video/media_kit_video.dart';
|
|||||||
class UniversalVideo extends ConsumerStatefulWidget {
|
class UniversalVideo extends ConsumerStatefulWidget {
|
||||||
final String uri;
|
final String uri;
|
||||||
final double aspectRatio;
|
final double aspectRatio;
|
||||||
|
final bool autoplay;
|
||||||
const UniversalVideo({
|
const UniversalVideo({
|
||||||
super.key,
|
super.key,
|
||||||
required this.uri,
|
required this.uri,
|
||||||
this.aspectRatio = 16 / 9,
|
this.aspectRatio = 16 / 9,
|
||||||
|
this.autoplay = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -47,7 +49,7 @@ class _UniversalVideoState extends ConsumerState<UniversalVideo> {
|
|||||||
log('[MediaPlayer] Hit cache: $url');
|
log('[MediaPlayer] Hit cache: $url');
|
||||||
}
|
}
|
||||||
|
|
||||||
_player!.open(Media(uri), play: false);
|
_player!.open(Media(uri), play: widget.autoplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
@@ -13,7 +14,10 @@ import 'package:island/pods/network.dart';
|
|||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
import 'package:island/services/compose_storage_db.dart';
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
@@ -60,6 +64,9 @@ class ComposeState {
|
|||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
_autoSaveTimer = null;
|
_autoSaveTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get isEmpty =>
|
||||||
|
attachments.value.isEmpty && contentController.text.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComposeLogic {
|
class ComposeLogic {
|
||||||
@@ -392,6 +399,95 @@ class ComposeLogic {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> linkAttachment(
|
||||||
|
WidgetRef ref,
|
||||||
|
ComposeState state,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
final TextEditingController idController = TextEditingController();
|
||||||
|
String? errorMessage;
|
||||||
|
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext dialogContext) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, setState) {
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText: 'linkAttachment'.tr(),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: idController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'fileId'.tr(),
|
||||||
|
helperText: 'fileIdHint'.tr(),
|
||||||
|
helperMaxLines: 3,
|
||||||
|
errorText: errorMessage,
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton.icon(
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
label: Text('add'.tr()),
|
||||||
|
onPressed: () async {
|
||||||
|
final fileId = idController.text.trim();
|
||||||
|
if (fileId.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
errorMessage = 'fileIdCannotBeEmpty'.tr();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final response = await client.get(
|
||||||
|
'/drive/files/$fileId/info',
|
||||||
|
);
|
||||||
|
final SnCloudFile cloudFile = SnCloudFile.fromJson(
|
||||||
|
response.data,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.attachments.value = [
|
||||||
|
...state.attachments.value,
|
||||||
|
UniversalFile(
|
||||||
|
data: cloudFile,
|
||||||
|
type: switch (cloudFile.mimeType
|
||||||
|
?.split('/')
|
||||||
|
.firstOrNull) {
|
||||||
|
'image' => UniversalFileType.image,
|
||||||
|
'video' => UniversalFileType.video,
|
||||||
|
'audio' => UniversalFileType.audio,
|
||||||
|
_ => UniversalFileType.file,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
errorMessage = 'failedToFetchFile'.tr(
|
||||||
|
args: [e.toString()],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 24),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> uploadAttachment(
|
static Future<void> uploadAttachment(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
ComposeState state,
|
ComposeState state,
|
||||||
|
|||||||
107
lib/widgets/post/compose_toolbar.dart
Normal file
107
lib/widgets/post/compose_toolbar.dart
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
|
import 'package:island/widgets/post/draft_manager.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class ComposeToolbar extends HookConsumerWidget {
|
||||||
|
final ComposeState state;
|
||||||
|
final SnPost? originalPost;
|
||||||
|
|
||||||
|
const ComposeToolbar({super.key, required this.state, this.originalPost});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
void pickPhotoMedia() {
|
||||||
|
ComposeLogic.pickPhotoMedia(ref, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pickVideoMedia() {
|
||||||
|
ComposeLogic.pickVideoMedia(ref, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void linkAttachment() {
|
||||||
|
ComposeLogic.linkAttachment(ref, state, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveDraft() {
|
||||||
|
ComposeLogic.saveDraft(ref, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showDraftManager() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => DraftManagerSheet(
|
||||||
|
onDraftSelected: (draftId) {
|
||||||
|
final draft = ref.read(composeStorageNotifierProvider)[draftId];
|
||||||
|
if (draft != null) {
|
||||||
|
state.titleController.text = draft.title ?? '';
|
||||||
|
state.descriptionController.text = draft.description ?? '';
|
||||||
|
state.contentController.text = draft.content ?? '';
|
||||||
|
state.visibility.value = draft.visibility;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
elevation: 4,
|
||||||
|
child: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: pickPhotoMedia,
|
||||||
|
tooltip: 'addPhoto'.tr(),
|
||||||
|
icon: const Icon(Symbols.add_a_photo),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: pickVideoMedia,
|
||||||
|
tooltip: 'addVideo'.tr(),
|
||||||
|
icon: const Icon(Symbols.videocam),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: linkAttachment,
|
||||||
|
icon: const Icon(Symbols.attach_file),
|
||||||
|
tooltip: 'linkAttachment'.tr(),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (originalPost == null && state.isEmpty)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.draft),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
onPressed: showDraftManager,
|
||||||
|
tooltip: 'drafts'.tr(),
|
||||||
|
)
|
||||||
|
else if (originalPost == null)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
color: colorScheme.primary,
|
||||||
|
onPressed: saveDraft,
|
||||||
|
onLongPress: showDraftManager,
|
||||||
|
tooltip: 'saveDraft'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||||
|
horizontal: 16,
|
||||||
|
top: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
152
lib/widgets/post/post_item.g.dart
Normal file
152
lib/widgets/post/post_item.g.dart
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'post_item.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$postFeaturedReplyHash() => r'3f0ac0d51ad21f8754a63dd94109eb8ac4812293';
|
||||||
|
|
||||||
|
/// 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 [postFeaturedReply].
|
||||||
|
@ProviderFor(postFeaturedReply)
|
||||||
|
const postFeaturedReplyProvider = PostFeaturedReplyFamily();
|
||||||
|
|
||||||
|
/// See also [postFeaturedReply].
|
||||||
|
class PostFeaturedReplyFamily extends Family<AsyncValue<SnPost?>> {
|
||||||
|
/// See also [postFeaturedReply].
|
||||||
|
const PostFeaturedReplyFamily();
|
||||||
|
|
||||||
|
/// See also [postFeaturedReply].
|
||||||
|
PostFeaturedReplyProvider call(String id) {
|
||||||
|
return PostFeaturedReplyProvider(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PostFeaturedReplyProvider getProviderOverride(
|
||||||
|
covariant PostFeaturedReplyProvider 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'postFeaturedReplyProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [postFeaturedReply].
|
||||||
|
class PostFeaturedReplyProvider extends AutoDisposeFutureProvider<SnPost?> {
|
||||||
|
/// See also [postFeaturedReply].
|
||||||
|
PostFeaturedReplyProvider(String id)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => postFeaturedReply(ref as PostFeaturedReplyRef, id),
|
||||||
|
from: postFeaturedReplyProvider,
|
||||||
|
name: r'postFeaturedReplyProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$postFeaturedReplyHash,
|
||||||
|
dependencies: PostFeaturedReplyFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
PostFeaturedReplyFamily._allTransitiveDependencies,
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
|
||||||
|
PostFeaturedReplyProvider._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
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<SnPost?> Function(PostFeaturedReplyRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PostFeaturedReplyProvider._internal(
|
||||||
|
(ref) => create(ref as PostFeaturedReplyRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
id: id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<SnPost?> createElement() {
|
||||||
|
return _PostFeaturedReplyProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PostFeaturedReplyProvider && 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 PostFeaturedReplyRef on AutoDisposeFutureProviderRef<SnPost?> {
|
||||||
|
/// The parameter `id` of this provider.
|
||||||
|
String get id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostFeaturedReplyProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<SnPost?>
|
||||||
|
with PostFeaturedReplyRef {
|
||||||
|
_PostFeaturedReplyProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => (origin as PostFeaturedReplyProvider).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
|
||||||
@@ -45,11 +45,13 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
title: 'edit'.tr(),
|
title: 'edit'.tr(),
|
||||||
image: MenuImage.icon(Symbols.edit),
|
image: MenuImage.icon(Symbols.edit),
|
||||||
callback: () {
|
callback: () {
|
||||||
context.pushNamed('postEdit', pathParameters: {'id': item.id}).then((value) {
|
context
|
||||||
if (value != null) {
|
.pushNamed('postEdit', pathParameters: {'id': item.id})
|
||||||
onRefresh?.call();
|
.then((value) {
|
||||||
}
|
if (value != null) {
|
||||||
});
|
onRefresh?.call();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MenuAction(
|
MenuAction(
|
||||||
@@ -80,7 +82,10 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
image: MenuImage.icon(Symbols.link),
|
image: MenuImage.icon(Symbols.link),
|
||||||
callback: () {
|
callback: () {
|
||||||
// Copy post link to clipboard
|
// Copy post link to clipboard
|
||||||
context.pushNamed('postDetail', pathParameters: {'id': item.id});
|
context.pushNamed(
|
||||||
|
'postDetail',
|
||||||
|
pathParameters: {'id': item.id},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -88,8 +93,6 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: Material(
|
child: Material(
|
||||||
color: backgroundColor ?? Theme.of(context).colorScheme.surface,
|
color: backgroundColor ?? Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
elevation: 1,
|
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -197,8 +200,8 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
CloudFileList(
|
CloudFileList(
|
||||||
files: item.attachments,
|
files: item.attachments,
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.85,
|
maxWidth: MediaQuery.of(context).size.width * 0.85,
|
||||||
minWidth: MediaQuery.of(context).size.width * 0.9,
|
padding: EdgeInsets.only(top: 8),
|
||||||
).padding(top: 8),
|
),
|
||||||
|
|
||||||
// Reference post indicator
|
// Reference post indicator
|
||||||
if (item.repliedPost != null || item.forwardedPost != null)
|
if (item.repliedPost != null || item.forwardedPost != null)
|
||||||
@@ -211,7 +214,7 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
item.repliedPost != null
|
item.repliedPost != null
|
||||||
? 'repliedTo'.tr()
|
? 'repliedTo'.tr()
|
||||||
@@ -364,6 +367,7 @@ class PostItemCreator extends HookConsumerWidget {
|
|||||||
PostReactionList(
|
PostReactionList(
|
||||||
parentId: item.id,
|
parentId: item.id,
|
||||||
reactions: item.reactionsCount,
|
reactions: item.reactionsCount,
|
||||||
|
reactionsMade: item.reactionsMade,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
|
|||||||
@@ -94,9 +94,7 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
|
|
||||||
final post = data.items[index];
|
final post = data.items[index];
|
||||||
|
|
||||||
return Column(
|
return _buildPostItem(post);
|
||||||
children: [_buildPostItem(post), const Divider(height: 1)],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -105,16 +103,24 @@ class SliverPostList extends HookConsumerWidget {
|
|||||||
Widget _buildPostItem(SnPost post) {
|
Widget _buildPostItem(SnPost post) {
|
||||||
switch (itemType) {
|
switch (itemType) {
|
||||||
case PostItemType.creator:
|
case PostItemType.creator:
|
||||||
return PostItemCreator(
|
return Column(
|
||||||
item: post,
|
children: [
|
||||||
backgroundColor: backgroundColor,
|
PostItemCreator(
|
||||||
padding: padding,
|
item: post,
|
||||||
isOpenable: isOpenable,
|
backgroundColor: backgroundColor,
|
||||||
onRefresh: onRefresh,
|
padding: padding,
|
||||||
onUpdate: onUpdate,
|
isOpenable: isOpenable,
|
||||||
|
onRefresh: onRefresh,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
case PostItemType.regular:
|
case PostItemType.regular:
|
||||||
return PostItem(item: post);
|
return Card(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
child: PostActionableItem(item: post, borderRadius: 8),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@@ -56,17 +55,17 @@ class PostRepliesNotifier extends _$PostRepliesNotifier
|
|||||||
|
|
||||||
class PostRepliesList extends HookConsumerWidget {
|
class PostRepliesList extends HookConsumerWidget {
|
||||||
final String postId;
|
final String postId;
|
||||||
final Color? backgroundColor;
|
final double? maxWidth;
|
||||||
|
final VoidCallback? onOpen;
|
||||||
const PostRepliesList({
|
const PostRepliesList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.postId,
|
required this.postId,
|
||||||
this.backgroundColor,
|
this.maxWidth,
|
||||||
|
this.onOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isWide = isWideScreen(context);
|
|
||||||
|
|
||||||
return PagingHelperSliverView(
|
return PagingHelperSliverView(
|
||||||
provider: postRepliesNotifierProvider(postId),
|
provider: postRepliesNotifierProvider(postId),
|
||||||
futureRefreshable: postRepliesNotifierProvider(postId).future,
|
futureRefreshable: postRepliesNotifierProvider(postId).future,
|
||||||
@@ -93,16 +92,24 @@ class PostRepliesList extends HookConsumerWidget {
|
|||||||
return endItemView;
|
return endItemView;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
final contentWidget = Card(
|
||||||
children: [
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
PostItem(
|
child: PostActionableItem(
|
||||||
item: data.items[index],
|
borderRadius: 8,
|
||||||
backgroundColor:
|
item: data.items[index],
|
||||||
backgroundColor ?? (isWide ? Colors.transparent : null),
|
isShowReference: false,
|
||||||
showReferencePost: false,
|
isEmbedOpenable: true,
|
||||||
),
|
onOpen: onOpen,
|
||||||
const Divider(height: 1),
|
),
|
||||||
],
|
);
|
||||||
|
|
||||||
|
if (maxWidth == null) return contentWidget;
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth!),
|
||||||
|
child: contentWidget,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ class PostRepliesSheet extends HookConsumerWidget {
|
|||||||
slivers: [
|
slivers: [
|
||||||
PostRepliesList(
|
PostRepliesList(
|
||||||
postId: post.id.toString(),
|
postId: post.id.toString(),
|
||||||
backgroundColor: Colors.transparent,
|
onOpen: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,9 +27,13 @@ class PublisherCard extends ConsumerWidget {
|
|||||||
|
|
||||||
Widget card = Card(
|
Widget card = Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('publisherProfile', pathParameters: {'name': publisher.name});
|
context.pushNamed(
|
||||||
|
'publisherProfile',
|
||||||
|
pathParameters: {'name': publisher.name},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 7,
|
aspectRatio: 16 / 7,
|
||||||
|
|||||||
@@ -29,9 +29,13 @@ class RealmCard extends ConsumerWidget {
|
|||||||
|
|
||||||
Widget card = Card(
|
Widget card = Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('realmDetail', pathParameters: {'slug': realm.slug});
|
context.pushNamed(
|
||||||
|
'realmDetail',
|
||||||
|
pathParameters: {'slug': realm.slug},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 7,
|
aspectRatio: 16 / 7,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class WebArticleCard extends StatelessWidget {
|
|||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
child: Card(
|
child: Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => _onTap(context),
|
onTap: () => _onTap(context),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <flutter_udid/flutter_udid_plugin.h>
|
#include <flutter_udid/flutter_udid_plugin.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||||
#include <irondash_engine_context/irondash_engine_context_plugin.h>
|
#include <irondash_engine_context/irondash_engine_context_plugin.h>
|
||||||
|
#include <livekit_client/live_kit_plugin.h>
|
||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin.h>
|
#include <media_kit_video/media_kit_video_plugin.h>
|
||||||
#include <pasteboard/pasteboard_plugin.h>
|
#include <pasteboard/pasteboard_plugin.h>
|
||||||
@@ -48,6 +49,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
|
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
|
||||||
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
|
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) livekit_client_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "LiveKitPlugin");
|
||||||
|
live_kit_plugin_register_with_registrar(livekit_client_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||||
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
flutter_udid
|
flutter_udid
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
irondash_engine_context
|
irondash_engine_context
|
||||||
|
livekit_client
|
||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
media_kit_video
|
media_kit_video
|
||||||
pasteboard
|
pasteboard
|
||||||
|
|||||||
@@ -11,34 +11,34 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- file_selector_macos (0.0.1):
|
- file_selector_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- Firebase/CoreOnly (11.15.0):
|
- Firebase/CoreOnly (12.0.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.0.0)
|
||||||
- Firebase/Messaging (11.15.0):
|
- Firebase/Messaging (12.0.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.15.0)
|
- FirebaseMessaging (~> 12.0.0)
|
||||||
- firebase_core (3.15.2):
|
- firebase_core (4.0.0):
|
||||||
- Firebase/CoreOnly (~> 11.15.0)
|
- Firebase/CoreOnly (~> 12.0.0)
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_messaging (15.2.10):
|
- firebase_messaging (16.0.0):
|
||||||
- Firebase/CoreOnly (~> 11.15.0)
|
- Firebase/CoreOnly (~> 12.0.0)
|
||||||
- Firebase/Messaging (~> 11.15.0)
|
- Firebase/Messaging (~> 12.0.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FirebaseCore (11.15.0):
|
- FirebaseCore (12.0.0):
|
||||||
- FirebaseCoreInternal (~> 11.15.0)
|
- FirebaseCoreInternal (~> 12.0.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreInternal (11.15.0):
|
- FirebaseCoreInternal (12.0.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseInstallations (11.15.0):
|
- FirebaseInstallations (12.0.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.0.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (11.15.0):
|
- FirebaseMessaging (12.0.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.0.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 12.0.0)
|
||||||
- GoogleDataTransport (~> 10.0)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Reachability (~> 8.1)
|
- GoogleUtilities/Reachability (~> 8.1)
|
||||||
@@ -56,9 +56,9 @@ PODS:
|
|||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
- flutter_webrtc (0.14.0):
|
- flutter_webrtc (1.0.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 125.6422.07)
|
- WebRTC-SDK (= 137.7151.02)
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -92,10 +92,10 @@ PODS:
|
|||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
- irondash_engine_context (0.0.1):
|
- irondash_engine_context (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- livekit_client (2.4.9):
|
- livekit_client (2.5.0):
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 125.6422.07)
|
- WebRTC-SDK (= 137.7151.02)
|
||||||
- local_auth_darwin (0.0.1):
|
- local_auth_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -143,6 +143,8 @@ PODS:
|
|||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.50.3):
|
- sqlite3/rtree (3.50.3):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
|
- sqlite3/session (3.50.3):
|
||||||
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -152,6 +154,7 @@ PODS:
|
|||||||
- sqlite3/math
|
- sqlite3/math
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
|
- sqlite3/session
|
||||||
- super_native_extensions (0.0.1):
|
- super_native_extensions (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- url_launcher_macos (0.0.1):
|
- url_launcher_macos (0.0.1):
|
||||||
@@ -160,7 +163,7 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (125.6422.07)
|
- WebRTC-SDK (137.7151.02)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
||||||
@@ -291,25 +294,25 @@ SPEC CHECKSUMS:
|
|||||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||||
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: 800d487043c0557d9faed71477a38d9aafb08a41
|
||||||
firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326
|
firebase_core: eeea10f64026b68cd0bc3dee079ab4717e22909e
|
||||||
firebase_messaging: df39858bcbbcce792c9e4f1ca51b41123d6181fd
|
firebase_messaging: 5eefcd5bde556bfacdd9968e11c52f39032dfbe5
|
||||||
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
FirebaseCore: 055f4ab117d5964158c833f3d5e7ec6d91648d4a
|
||||||
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
FirebaseCoreInternal: dedc28e569a4be85f38f3d6af1070a2e12018d55
|
||||||
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
FirebaseInstallations: d4c7c958f99c8860d7fcece786314ae790e2f988
|
||||||
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
|
FirebaseMessaging: af49f8d7c0a3d2a017d9302c80946f45a7777dde
|
||||||
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
|
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
|
||||||
flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
|
flutter_platform_alert: 8fa7a7c21f95b26d08b4a3891936ca27e375f284
|
||||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||||
flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8
|
flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8
|
||||||
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
|
flutter_udid: d26e455e8c06174e6aff476e147defc6cae38495
|
||||||
flutter_webrtc: a7eeb54859e672228c28f4b48b1fb61561976ea3
|
flutter_webrtc: 0d70bd8782c19bde286dc52f766eebbea26de201
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||||
livekit_client: c9d9f41996f5cf22b9ba0e8483e6af4ca5094059
|
livekit_client: 0b0515e03858b86a7c14cc7fd6f772331f6ee84c
|
||||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||||
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
||||||
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
||||||
@@ -326,12 +329,12 @@ SPEC CHECKSUMS:
|
|||||||
sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
|
sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
||||||
sqlite3_flutter_libs: ce0522d143cee6ef5e16587acfce8f476316e005
|
sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
|
||||||
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
|
||||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||||
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
||||||
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
|
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
|
||||||
WebRTC-SDK: dff00a3892bc570b6014e046297782084071657e
|
WebRTC-SDK: d20de357dcbf7c9696b124b39f3ff62125107e4b
|
||||||
|
|
||||||
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
PODFILE CHECKSUM: 346bfb2deb41d4a6ebd6f6799f92188bde2d246f
|
||||||
|
|
||||||
|
|||||||
76
pubspec.lock
76
pubspec.lock
@@ -13,10 +13,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _flutterfire_internals
|
name: _flutterfire_internals
|
||||||
sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
|
sha256: bb84ee51e527053dd8e25ecc9f97a6abfdc19130fb4d883e4e8585e23e7e6dd8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.59"
|
version: "1.3.60"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -421,10 +421,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_webrtc
|
name: dart_webrtc
|
||||||
sha256: "5b76fd85ac95d6f5dee3e7d7de8d4b51bfbec1dc73804647c6aebb52d6297116"
|
sha256: a2ae542cdadc21359022adedc26138fa3487cc3b3547c24ff4f556681869e28c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.3+hotfix.2"
|
version: "1.5.3+hotfix.4"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -477,10 +477,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift
|
name: drift
|
||||||
sha256: dce2723fb0dd03563af21f305f8f96514c27f870efba934b4fe84d4fedb4eff7
|
sha256: "6aaea757f53bb035e8a3baedf3d1d53a79d6549a6c13d84f7546509da9372c7c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.28.0"
|
version: "2.28.1"
|
||||||
drift_dev:
|
drift_dev:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -509,10 +509,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: easy_localization
|
name: easy_localization
|
||||||
sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12"
|
sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7+1"
|
version: "3.0.8"
|
||||||
easy_logger:
|
easy_logger:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -613,10 +613,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_core
|
name: firebase_core
|
||||||
sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
|
sha256: "6b343e6f7b72a4f32d7ce8df8c9a28d8f54b4ac20d7c6500f3e8b3969afca457"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.15.2"
|
version: "4.0.0"
|
||||||
firebase_core_platform_interface:
|
firebase_core_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -629,34 +629,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_web
|
name: firebase_core_web
|
||||||
sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
|
sha256: "5d28b14dd32282fb7ce2b22b897362453755b6b8541d491127dc72b755bb7b16"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.24.1"
|
version: "3.0.0"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging
|
name: firebase_messaging
|
||||||
sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc"
|
sha256: "10272b553a49c13a6cedfd00121047157521f82a5d3f2a1706b9dd28342cc482"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.10"
|
version: "16.0.0"
|
||||||
firebase_messaging_platform_interface:
|
firebase_messaging_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_platform_interface
|
name: firebase_messaging_platform_interface
|
||||||
sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754"
|
sha256: b846a305feb3f74ee3f0aace447f65a4696bc6550bc828ecf5a84a1b77473d16
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.10"
|
version: "4.7.0"
|
||||||
firebase_messaging_web:
|
firebase_messaging_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_messaging_web
|
name: firebase_messaging_web
|
||||||
sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390"
|
sha256: "28714749880f7242c5fb3b1ee6c66b41f61453f02ae348b43c82957df80b87ae"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.10.10"
|
version: "4.0.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -830,6 +830,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
flutter_langdetect:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_langdetect
|
||||||
|
sha256: "93bd865c7d5723eac614744abb32234ee4f593505a293bc17ef097bd55fbdf38"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.2"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -1017,10 +1025,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: "792aa1e5838a719f29ae52c0773dbb5dd781fc33b1bf87c321b274e55ab51ad1"
|
sha256: "69095ba39b83da3de48286dfc0769aa8e9f10491f70058dc8d8ecc960ef7a260"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.14.2"
|
version: "1.0.0"
|
||||||
font_awesome_flutter:
|
font_awesome_flutter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1337,10 +1345,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: livekit_client
|
name: livekit_client
|
||||||
sha256: "5d182f40cc9aafce60a9acf936bad8bc69010b5cbf0a949f6f27dc4390f2fcce"
|
sha256: b3db2d8afa8d1dbe4fd8dfc965fc9d661cb51a8d864ad199919575ce919a40fb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.9"
|
version: "2.5.0+hotfix.1"
|
||||||
local_auth:
|
local_auth:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1381,6 +1389,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.11"
|
version: "1.0.11"
|
||||||
|
logger:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logger
|
||||||
|
sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1433,10 +1449,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: material_symbols_icons
|
name: material_symbols_icons
|
||||||
sha256: "7c50901b39d1ad645ee25d920aed008061e1fd541a897b4ebf2c01d966dbf16b"
|
sha256: ef20d86fb34c2b59eb7553c4d795bb8a7ec8c890c53ffd3148c64f7adc46ae50
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2815.1"
|
version: "4.2858.1"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -2166,10 +2182,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
|
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.5"
|
version: "2.5.6"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2190,18 +2206,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: "608b56d594e4c8498c972c8f1507209f9fd74939971b948ddbbfbfd1c9cb3c15"
|
sha256: dd806fff004a0aeb01e208b858dbc649bc72104670d425a81a6dd17698535f6e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.7"
|
version: "2.8.0"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: "60464aa06f3f6f6fba9abd7564e315526c1fee6d6a77d6ee52a1f7f48a9107f6"
|
sha256: fd996da5515a73aacd0a04ae7063db5fe8df42670d974df4c3ee538c652eef2e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.37"
|
version: "0.5.38"
|
||||||
sqlparser:
|
sqlparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
53
pubspec.yaml
53
pubspec.yaml
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 3.1.0+115
|
version: 3.1.0+116
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.2
|
sdk: ^3.7.2
|
||||||
@@ -46,65 +46,65 @@ dependencies:
|
|||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
dio: ^5.8.0+1
|
dio: ^5.8.0+1
|
||||||
very_good_infinite_list: ^0.9.0
|
very_good_infinite_list: ^0.9.0
|
||||||
freezed_annotation: ^3.0.0
|
freezed_annotation: ^3.1.0
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
flutter_markdown_latex: ^0.3.4
|
flutter_markdown_latex: ^0.3.4
|
||||||
markdown: ^7.3.0
|
markdown: ^7.3.0
|
||||||
flutter_highlight: ^0.7.0
|
flutter_highlight: ^0.7.0
|
||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.2
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
gap: ^3.0.1
|
gap: ^3.0.1
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.4.1
|
||||||
web: ^1.1.1
|
web: ^1.1.1
|
||||||
flutter_blurhash: ^0.9.0
|
flutter_blurhash: ^0.9.1
|
||||||
media_kit: ^1.2.0
|
media_kit: ^1.2.0
|
||||||
media_kit_video: ^1.3.0
|
media_kit_video: ^1.3.0
|
||||||
media_kit_libs_video: ^1.0.6
|
media_kit_libs_video: ^1.0.6
|
||||||
flutter_cache_manager: ^3.4.1
|
flutter_cache_manager: ^3.4.1
|
||||||
flutter_platform_alert: ^0.8.0
|
flutter_platform_alert: ^0.8.0
|
||||||
email_validator: ^3.0.0
|
email_validator: ^3.0.0
|
||||||
easy_localization: ^3.0.7+1
|
easy_localization: ^3.0.8
|
||||||
flutter_inappwebview: ^6.1.5
|
flutter_inappwebview: ^6.1.5
|
||||||
animations: ^2.0.11
|
animations: ^2.0.11
|
||||||
package_info_plus: ^8.3.0
|
package_info_plus: ^8.3.0
|
||||||
device_info_plus: ^11.4.0
|
device_info_plus: ^11.5.0
|
||||||
tus_client_dart:
|
tus_client_dart:
|
||||||
git: https://github.com/LittleSheep2Code/tus_client.git
|
git: https://github.com/LittleSheep2Code/tus_client.git
|
||||||
cross_file: ^0.3.4+2
|
cross_file: ^0.3.4+2
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
file_picker: ^10.1.7
|
file_picker: ^10.2.0
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
image_picker_platform_interface: ^2.10.1
|
image_picker_platform_interface: ^2.10.1
|
||||||
image_picker_android: ^0.8.12+23
|
image_picker_android: ^0.8.12+24
|
||||||
super_context_menu: ^0.9.0-dev.6
|
super_context_menu: ^0.9.1
|
||||||
modal_bottom_sheet: ^3.0.0
|
modal_bottom_sheet: ^3.0.0
|
||||||
firebase_messaging: ^15.2.5
|
firebase_messaging: ^16.0.0
|
||||||
flutter_udid: ^4.0.0
|
flutter_udid: ^4.0.0
|
||||||
firebase_core: ^3.13.0
|
firebase_core: ^4.0.0
|
||||||
web_socket_channel: ^3.0.3
|
web_socket_channel: ^3.0.3
|
||||||
material_symbols_icons: ^4.2815.0
|
material_symbols_icons: ^4.2858.1
|
||||||
drift: ^2.26.0
|
drift: ^2.28.1
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.2.5
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
markdown_editor_plus: ^0.2.15
|
markdown_editor_plus: ^0.2.15
|
||||||
croppy: ^1.3.6
|
croppy: ^1.3.6
|
||||||
table_calendar: ^3.1.3
|
table_calendar: ^3.2.0
|
||||||
relative_time: ^5.0.0
|
relative_time: ^5.0.0
|
||||||
dropdown_button2: ^2.3.9
|
dropdown_button2: ^2.3.9
|
||||||
riverpod_paging_utils: ^0.8.0
|
riverpod_paging_utils: ^0.8.1
|
||||||
crypto: ^3.0.6
|
crypto: ^3.0.6
|
||||||
avatar_stack: ^3.0.0
|
avatar_stack: ^3.0.0
|
||||||
markdown_widget: ^2.3.2+8
|
markdown_widget: ^2.3.2+8
|
||||||
visibility_detector: ^0.4.0+2
|
visibility_detector: ^0.4.0+2
|
||||||
flutter_native_splash: ^2.4.6
|
flutter_native_splash: ^2.4.6
|
||||||
photo_view: ^0.15.0
|
photo_view: ^0.15.0
|
||||||
gal: ^2.3.1
|
gal: ^2.3.2
|
||||||
dismissible_page: ^1.0.2
|
dismissible_page: ^1.0.2
|
||||||
super_sliver_list: ^0.4.1
|
super_sliver_list: ^0.4.1
|
||||||
flutter_webrtc: ^0.14.1
|
flutter_webrtc: ^1.0.0
|
||||||
livekit_client: ^2.4.7
|
livekit_client: ^2.5.0+hotfix.1
|
||||||
pasteboard: ^0.4.0
|
pasteboard: ^0.4.0
|
||||||
flutter_colorpicker: ^1.1.0
|
flutter_colorpicker: ^1.1.0
|
||||||
record: ^6.0.0
|
record: ^6.0.0
|
||||||
@@ -116,7 +116,7 @@ dependencies:
|
|||||||
flutter_timezone: ^4.1.1
|
flutter_timezone: ^4.1.1
|
||||||
fl_chart: ^1.0.0
|
fl_chart: ^1.0.0
|
||||||
sign_in_with_apple: ^7.0.1
|
sign_in_with_apple: ^7.0.1
|
||||||
flutter_svg: ^2.1.0
|
flutter_svg: ^2.2.0
|
||||||
native_exif: ^0.6.2
|
native_exif: ^0.6.2
|
||||||
local_auth: ^2.3.0
|
local_auth: ^2.3.0
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
@@ -131,6 +131,7 @@ dependencies:
|
|||||||
mime: ^2.0.0
|
mime: ^2.0.0
|
||||||
html2md: ^1.3.2
|
html2md: ^1.3.2
|
||||||
flutter_typeahead: ^5.2.0
|
flutter_typeahead: ^5.2.0
|
||||||
|
flutter_langdetect: ^0.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -142,15 +143,15 @@ dev_dependencies:
|
|||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
auto_route_generator: ^10.0.1
|
auto_route_generator: ^10.1.0
|
||||||
build_runner: ^2.4.15
|
build_runner: ^2.5.4
|
||||||
freezed: ^3.0.6
|
freezed: ^3.1.0
|
||||||
json_serializable: ^6.9.5
|
json_serializable: ^6.9.5
|
||||||
riverpod_generator: ^2.6.5
|
riverpod_generator: ^2.6.5
|
||||||
custom_lint: ^0.7.5
|
custom_lint: ^0.7.6
|
||||||
riverpod_lint: ^2.6.5
|
riverpod_lint: ^2.6.5
|
||||||
drift_dev: ^2.26.0
|
drift_dev: ^2.28.0
|
||||||
flutter_launcher_icons: ^0.14.3
|
flutter_launcher_icons: ^0.14.4
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
Reference in New Issue
Block a user