Compare commits

...

27 Commits

Author SHA1 Message Date
3ac510c4b1 🐛 Bug fixes 2024-08-20 01:19:18 +08:00
253cd1ecbd Call in same screen on large screen 2024-08-20 01:10:15 +08:00
c82c48dfec 🐛 Fix attachments padding 2024-08-19 22:45:22 +08:00
433beec2dd 💄 Optimize large screen ux 2024-08-19 22:38:36 +08:00
3a1e7537dd 🐛 Fix alignment issue 2024-08-19 22:25:49 +08:00
9170ae6be7 💄 Line up attachments & expansion of link 2024-08-19 22:25:17 +08:00
a5ee5b7f09 💄 Better attachment layout 2024-08-19 22:13:25 +08:00
32e6658f3d Better link expand layout on large screen 2024-08-19 20:13:08 +08:00
e45d9b39d5 Post link expand
 Cache link expansion image
2024-08-19 19:56:44 +08:00
cf1cfecb08 Link expand 2024-08-19 19:36:01 +08:00
95ea3e558f 🚀 Launch 1.2.1+18 2024-08-19 09:43:25 +08:00
0006a94632 🐛 Fix local db old data cause crash 2024-08-19 09:19:29 +08:00
7ea18dbe12 💄 Update styles 2024-08-19 01:54:32 +08:00
6004b74724 🚀 Launch 1.2.1+17 2024-08-19 01:35:57 +08:00
4d82ae8058 🐛 Bug fixes
⬆️ Add firebase performance
2024-08-19 01:35:38 +08:00
7fe26d0df0 🚀 Launch 1.2.1+16 2024-08-19 00:33:20 +08:00
80bade0e03 View posts posted by friends 2024-08-19 00:33:03 +08:00
b63db7fe76 👽 Support use realm alias instead of id 2024-08-19 00:14:09 +08:00
49f73f5f04 ⬆️ Support new attachments system 2024-08-18 22:51:52 +08:00
98749f42c0 ⬆️ Upgrade deps 2024-08-17 19:18:51 +08:00
f0e6bd64f4 ♻️ Refactor video player 2024-08-17 19:02:57 +08:00
3bea3a114a Post alias 2024-08-17 18:44:20 +08:00
454f711656 ⬆️ Upgrade deps 2024-08-16 23:27:38 +08:00
82e4c923e7 📈 Simple log user share 2024-08-16 23:08:05 +08:00
5b4d8282ae Re-google (firebase) 2024-08-16 22:59:34 +08:00
cf767a1d94 💄 Optimized post editor 2024-08-16 21:06:50 +08:00
af93a8386a ⬆️ Upgrade deps 2024-08-16 01:05:21 +08:00
59 changed files with 1582 additions and 703 deletions

View File

@ -1,6 +1,7 @@
plugins {
id "com.android.application"
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}

View File

@ -20,6 +20,7 @@ plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.4.0' apply false
id "com.google.gms.google-services" version "4.3.15" apply false
id "com.google.firebase.crashlytics" version "2.8.1" apply false
id "org.jetbrains.kotlin.android" version '2.0.0' apply false
}

View File

@ -38,24 +38,78 @@ PODS:
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Firebase/Analytics (10.29.0):
- Firebase/Core
- Firebase/Core (10.29.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 10.29.0)
- Firebase/CoreOnly (10.29.0):
- FirebaseCore (= 10.29.0)
- Firebase/Crashlytics (10.29.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 10.29.0)
- Firebase/Messaging (10.29.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 10.29.0)
- Firebase/Performance (10.29.0):
- Firebase/CoreOnly
- FirebasePerformance (~> 10.29.0)
- firebase_analytics (11.2.1):
- Firebase/Analytics (= 10.29.0)
- firebase_core
- Flutter
- firebase_core (3.3.0):
- Firebase/CoreOnly (= 10.29.0)
- Flutter
- firebase_crashlytics (4.0.4):
- Firebase/Crashlytics (= 10.29.0)
- firebase_core
- Flutter
- firebase_messaging (15.0.4):
- Firebase/Messaging (= 10.29.0)
- firebase_core
- Flutter
- firebase_performance (0.10.0-4):
- Firebase/Performance (= 10.29.0)
- firebase_core
- Flutter
- FirebaseABTesting (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseAnalytics (10.29.0):
- FirebaseAnalytics/AdIdSupport (= 10.29.0)
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseAnalytics/AdIdSupport (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleAppMeasurement (= 10.29.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseCore (10.29.0):
- FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.12)
- GoogleUtilities/Logger (~> 7.12)
- FirebaseCoreExtension (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseCoreInternal (10.29.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- FirebaseCrashlytics (10.29.0):
- FirebaseCore (~> 10.5)
- FirebaseInstallations (~> 10.0)
- FirebaseRemoteConfigInterop (~> 10.23)
- FirebaseSessions (~> 10.5)
- GoogleDataTransport (~> 9.2)
- GoogleUtilities/Environment (~> 7.8)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesObjC (~> 2.1)
- FirebaseInstallations (10.29.0):
- FirebaseCore (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
@ -70,6 +124,36 @@ PODS:
- GoogleUtilities/Reachability (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebasePerformance (10.29.0):
- FirebaseCore (~> 10.5)
- FirebaseInstallations (~> 10.0)
- FirebaseRemoteConfig (~> 10.0)
- FirebaseSessions (~> 10.5)
- GoogleDataTransport (~> 9.2)
- GoogleUtilities/Environment (~> 7.13)
- GoogleUtilities/ISASwizzler (~> 7.13)
- GoogleUtilities/MethodSwizzler (~> 7.13)
- GoogleUtilities/UserDefaults (~> 7.13)
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseRemoteConfig (10.29.0):
- FirebaseABTesting (~> 10.0)
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- FirebaseRemoteConfigInterop (~> 10.23)
- FirebaseSharedSwift (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- FirebaseRemoteConfigInterop (10.29.0)
- FirebaseSessions (10.29.0):
- FirebaseCore (~> 10.5)
- FirebaseCoreExtension (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleDataTransport (~> 9.2)
- GoogleUtilities/Environment (~> 7.13)
- GoogleUtilities/UserDefaults (~> 7.13)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesSwift (~> 2.1)
- FirebaseSharedSwift (10.29.0)
- Flutter (1.0.0)
- flutter_keyboard_visibility (0.0.1):
- Flutter
@ -81,6 +165,26 @@ PODS:
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAppMeasurement (10.29.0):
- GoogleAppMeasurement/AdIdSupport (= 10.29.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (10.29.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 10.29.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (10.29.0):
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleDataTransport (9.4.1):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30911.0, >= 2.30908.0)
@ -93,9 +197,14 @@ PODS:
- GoogleUtilities/Environment (7.13.3):
- GoogleUtilities/Privacy
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/ISASwizzler (7.13.3):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/MethodSwizzler (7.13.3):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/Network (7.13.3):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
@ -115,7 +224,7 @@ PODS:
- TOCropViewController (~> 2.7.4)
- image_picker_ios (0.0.1):
- Flutter
- livekit_client (2.2.3):
- livekit_client (2.2.4):
- Flutter
- WebRTC-SDK (= 125.6422.04)
- media_kit_libs_ios_video (1.0.4):
@ -141,6 +250,8 @@ PODS:
- pointer_interceptor_ios (0.0.1):
- Flutter
- PromisesObjC (2.4.0)
- PromisesSwift (2.4.0):
- PromisesObjC (= 2.4.0)
- protocol_handler_ios (0.0.1):
- Flutter
- screen_brightness_ios (0.1.0):
@ -148,11 +259,6 @@ PODS:
- SDWebImage (5.19.6):
- SDWebImage/Core (= 5.19.6)
- SDWebImage/Core (5.19.6)
- Sentry/HybridSDK (8.33.0)
- sentry_flutter (8.7.0):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.33.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@ -175,8 +281,11 @@ DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- firebase_performance (from `.symlinks/plugins/firebase_performance/ios`)
- Flutter (from `Flutter`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
@ -195,7 +304,6 @@ DEPENDENCIES:
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
- protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`)
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
@ -208,16 +316,26 @@ SPEC REPOS:
- DKImagePickerController
- DKPhotoGallery
- Firebase
- FirebaseABTesting
- FirebaseAnalytics
- FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal
- FirebaseCrashlytics
- FirebaseInstallations
- FirebaseMessaging
- FirebasePerformance
- FirebaseRemoteConfig
- FirebaseRemoteConfigInterop
- FirebaseSessions
- FirebaseSharedSwift
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
- nanopb
- PromisesObjC
- PromisesSwift
- SDWebImage
- Sentry
- SwiftyGif
- TOCropViewController
- WebRTC-SDK
@ -229,10 +347,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
firebase_analytics:
:path: ".symlinks/plugins/firebase_analytics/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_crashlytics:
:path: ".symlinks/plugins/firebase_crashlytics/ios"
firebase_messaging:
:path: ".symlinks/plugins/firebase_messaging/ios"
firebase_performance:
:path: ".symlinks/plugins/firebase_performance/ios"
Flutter:
:path: Flutter
flutter_keyboard_visibility:
@ -269,8 +393,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/protocol_handler_ios/ios"
screen_brightness_ios:
:path: ".symlinks/plugins/screen_brightness_ios/ios"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
@ -291,22 +413,35 @@ SPEC CHECKSUMS:
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
Firebase: cec914dab6fd7b1bd8ab56ea07ce4e03dd251c2d
firebase_analytics: 04491d1ee74c8e7c2330c96afc54188a969b06ee
firebase_core: 57aeb91680e5d5e6df6b888064be7c785f146efb
firebase_crashlytics: e3d3e0c99bad5aaab5908385133dea8ec344693f
firebase_messaging: c862b3d2b973ecc769194dc8de09bd22c77ae757
firebase_performance: 8643e815a354ee94da1192cd69335a48a7b625a4
FirebaseABTesting: d87f56707159bae64e269757a6e963d490f2eebe
FirebaseAnalytics: 23717de130b779aa506e757edb9713d24b6ffeda
FirebaseCore: 30e9c1cbe3d38f5f5e75f48bfcea87d7c358ec16
FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f
FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934
FirebaseCrashlytics: 34647b41e18de773717fdd348a22206f2f9bc774
FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd
FirebaseMessaging: 7b5d8033e183ab59eb5b852a53201559e976d366
FirebasePerformance: d0ac4aa90f8c1aedeb8d0329a56e2d77d8d9e004
FirebaseRemoteConfig: 48ef3f243742a8d72422ccfc9f986e19d7de53fd
FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d
FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc
FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
livekit_client: bad83a7776a41abc42e1f26d903eeac9164c8a9f
livekit_client: d079c5f040d4bf2b80440ff0ae997725a183e4bc
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
@ -317,11 +452,10 @@ SPEC CHECKSUMS:
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
pointer_interceptor_ios: 508241697ff0947f853c061945a8b822463947c1
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: a79252b60f4678812d94316c91da69ec83089c9f
Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6
sentry_flutter: e26b861f744e5037a3faf9bf56603ec65d658a61
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec

View File

@ -254,6 +254,7 @@
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
B1CDA9DD5B638A2BB88053CB /* [CP] Check Pods Manifest.lock */,
7356FAC42C72724B0051A465 /* [Crashlytics] Clear dSYM */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
@ -263,6 +264,7 @@
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
287A33C298CA352A7E7F32A4 /* [CP] Embed Pods Frameworks */,
0818E8E4321C0D7433E07576 /* [CP] Copy Pods Resources */,
1A9FD6BE5DEE99CDA7399504 /* [Crashlytics] Upload dSYM */,
);
buildRules = (
);
@ -365,6 +367,24 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
1A9FD6BE5DEE99CDA7399504 /* [Crashlytics] Upload dSYM */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "[Crashlytics] Upload dSYM";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\n#!/bin/bash\nsleep 1 # Without this, there seems a chance that the script runs before dSYM generation is finished \n$PODS_ROOT/FirebaseCrashlytics/upload-symbols -gsp $PROJECT_DIR/Runner/GoogleService-Info.plist -p ios $DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME\n";
};
259653AE41D478F4C6BAE9B2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -420,6 +440,24 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
7356FAC42C72724B0051A465 /* [Crashlytics] Clear dSYM */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "[Crashlytics] Clear dSYM";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\n#!/bin/bash\nrm -rf $DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME\n";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -433,7 +471,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
};
B1CDA9DD5B638A2BB88053CB /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;

View File

@ -17,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart';
class PostEditorController extends GetxController {
late final SharedPreferences _prefs;
final aliasController = TextEditingController();
final titleController = TextEditingController();
final descriptionController = TextEditingController();
final contentController = TextEditingController();
@ -30,9 +31,9 @@ class PostEditorController extends GetxController {
Rx<Realm?> realmZone = Rx(null);
Rx<DateTime?> publishedAt = Rx(null);
Rx<DateTime?> publishedUntil = Rx(null);
RxList<int> attachments = RxList<int>.empty(growable: true);
RxList<String> attachments = RxList<String>.empty(growable: true);
RxList<String> tags = RxList<String>.empty(growable: true);
Rx<int?> thumbnail = Rx(null);
Rx<String?> thumbnail = Rx(null);
RxList<int> visibleUsers = RxList.empty(growable: true);
RxList<int> invisibleUsers = RxList.empty(growable: true);
@ -115,12 +116,12 @@ class PostEditorController extends GetxController {
return showModalBottomSheet(
context: context,
builder: (context) => AttachmentEditorPopup(
usage: 'i.attachment',
pool: 'interactive',
initialAttachments: attachments,
onAdd: (int value) {
onAdd: (String value) {
attachments.add(value);
},
onRemove: (int value) {
onRemove: (String value) {
attachments.remove(value);
},
),
@ -168,6 +169,7 @@ class PostEditorController extends GetxController {
}
void currentClear() {
aliasController.clear();
titleController.clear();
descriptionController.clear();
contentController.clear();
@ -197,16 +199,23 @@ class PostEditorController extends GetxController {
type = value.type;
editTo.value = value;
realmZone.value = value.realm;
isDraft.value = value.isDraft ?? false;
aliasController.text = value.alias ?? '';
titleController.text = value.body['title'] ?? '';
descriptionController.text = value.body['description'] ?? '';
contentController.text = value.body['content'] ?? '';
publishedAt.value = value.publishedAt;
publishedUntil.value = value.publishedUntil;
tags.value =
value.body['tags']?.map((x) => x['alias']).toList() ?? List.empty();
tags.value = List.from(
value.body['tags']?.map((x) => x['alias']).toList() ?? List.empty(),
growable: true,
);
tags.refresh();
attachments.value = value.body['attachments']?.cast<int>() ?? List.empty();
attachments.value = List.from(
value.body['attachments'] ?? List.empty(),
growable: true,
);
attachments.refresh();
thumbnail.value = value.body['thumbnail'];
@ -256,6 +265,7 @@ class PostEditorController extends GetxController {
Map<String, dynamic> get payload {
return {
'alias': aliasController.text,
'title': title,
'description': description,
'content': contentController.text,
@ -277,20 +287,33 @@ class PostEditorController extends GetxController {
set payload(Map<String, dynamic> value) {
type = value['type'];
tags.value = value['tags'].map((x) => x['alias']).toList().cast<String>();
tags.value = List.from(
value['tags'].map((x) => x['alias']).toList(),
growable: true,
);
aliasController.text = value['alias'] ?? '';
titleController.text = value['title'] ?? '';
descriptionController.text = value['description'] ?? '';
contentController.text = value['content'] ?? '';
attachments.value = value['attachments'].cast<int>() ?? List.empty();
attachments.value = List.from(
value['attachments'] ?? List.empty(),
growable: true,
);
attachments.refresh();
thumbnail.value = value['thumbnail'];
visibility.value = value['visibility'];
isDraft.value = value['is_draft'];
if (value['visible_users'] != null) {
visibleUsers.value = value['visible_users'].cast<int>();
visibleUsers.value = List.from(
value['visible_users'],
growable: true,
);
}
if (value['invisible_users'] != null) {
invisibleUsers.value = value['invisible_users'].cast<int>();
invisibleUsers.value = List.from(
value['invisible_users'],
growable: true,
);
}
if (value['published_at'] != null) {
publishedAt.value = DateTime.parse(value['published_at']).toLocal();
@ -319,6 +342,7 @@ class PostEditorController extends GetxController {
bool get isNotEmpty {
return [
aliasController.text.isNotEmpty,
titleController.text.isNotEmpty,
descriptionController.text.isNotEmpty,
contentController.text.isNotEmpty,

View File

@ -9,11 +9,12 @@ class PostListController extends GetxController {
/// The polling source modifier.
/// - `0`: default recommendations
/// - `1`: shuffle mode
/// - `1`: friend mode
/// - `2`: shuffle mode
RxInt mode = 0.obs;
/// The paging controller for infinite loading.
/// Only available when mode is `0`.
/// Only available when mode is `0` or `1`.
PagingController<int, Post> pagingController =
PagingController(firstPageKey: 0);
@ -111,10 +112,23 @@ class PostListController extends GetxController {
author: author,
);
} else {
resp = await provider.listRecommendations(
pageKey,
channel: mode.value == 0 ? null : 'shuffle',
);
switch (mode.value) {
case 2:
resp = await provider.listRecommendations(
pageKey,
channel: 'shuffle',
);
break;
case 1:
resp = await provider.listRecommendations(
pageKey,
channel: 'friends',
);
break;
default:
resp = await provider.listRecommendations(pageKey);
break;
}
}
} catch (e) {
rethrow;

View File

@ -85,4 +85,5 @@ class DefaultFirebaseOptions {
storageBucket: 'solian-0x001.appspot.com',
measurementId: 'G-EF9BZMKBC3',
);
}
}

View File

@ -1,4 +1,7 @@
import 'dart:ui';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:get/get.dart';
@ -6,11 +9,11 @@ import 'package:go_router/go_router.dart';
import 'package:media_kit/media_kit.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:provider/provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:solian/bootstrapper.dart';
import 'package:solian/firebase_options.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/attachment_uploader.dart';
import 'package:solian/providers/link_expander.dart';
import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart';
@ -29,32 +32,29 @@ import 'package:solian/translations.dart';
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
void main() async {
await SentryFlutter.init(
(options) {
options.dsn =
'https://55438cdff9048aa2225df72fdc629c42@o4506965897117696.ingest.us.sentry.io/4507357676437504';
options.tracesSampleRate = 1.0;
options.profilesSampleRate = 1.0;
},
appRunner: () async {
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
await Future.wait([
_initializeFirebase(),
_initializePlatformComponents(),
]);
await Future.wait([
_initializeFirebase(),
_initializePlatformComponents(),
]);
GoRouter.optionURLReflectsImperativeAPIs = true;
GoRouter.optionURLReflectsImperativeAPIs = true;
usePathUrlStrategy();
runApp(const SolianApp());
},
);
usePathUrlStrategy();
runApp(const SolianApp());
}
Future<void> _initializeFirebase() async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
FlutterError.onError = (errorDetails) {
FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
}
Future<void> _initializePlatformComponents() async {
@ -129,5 +129,6 @@ class SolianApp extends StatelessWidget {
Get.lazyPut(() => RealmProvider());
Get.lazyPut(() => ChatCallProvider());
Get.lazyPut(() => AttachmentUploaderController());
Get.lazyPut(() => LinkExpandController());
}
}

View File

@ -5,11 +5,11 @@ class Attachment {
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
String rid;
String uuid;
int size;
String name;
String alt;
String usage;
String mimetype;
String hash;
int destination;
@ -24,11 +24,11 @@ class Attachment {
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.rid,
required this.uuid,
required this.size,
required this.name,
required this.alt,
required this.usage,
required this.mimetype,
required this.hash,
required this.destination,
@ -40,42 +40,45 @@ class Attachment {
});
factory Attachment.fromJson(Map<String, dynamic> json) => Attachment(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null ? DateTime.parse(json['deleted_at']) : null,
uuid: json['uuid'],
size: json['size'],
name: json['name'],
alt: json['alt'],
usage: json['usage'],
mimetype: json['mimetype'],
hash: json['hash'],
destination: json['destination'],
isAnalyzed: json['is_analyzed'],
metadata: json['metadata'],
isMature: json['is_mature'],
account: json['account'] != null ? Account.fromJson(json['account']) : null,
accountId: json['account_id'],
);
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
rid: json['rid'],
uuid: json['uuid'],
size: json['size'],
name: json['name'],
alt: json['alt'],
mimetype: json['mimetype'],
hash: json['hash'],
destination: json['destination'],
isAnalyzed: json['is_analyzed'],
metadata: json['metadata'],
isMature: json['is_mature'],
account:
json['account'] != null ? Account.fromJson(json['account']) : null,
accountId: json['account_id'],
);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'uuid': uuid,
'size': size,
'name': name,
'alt': alt,
'usage': usage,
'mimetype': mimetype,
'hash': hash,
'destination': destination,
'is_analyzed': isAnalyzed,
'metadata': metadata,
'is_mature': isMature,
'account': account?.toJson(),
'account_id': accountId,
};
}
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'rid': rid,
'uuid': uuid,
'size': size,
'name': name,
'alt': alt,
'mimetype': mimetype,
'hash': hash,
'destination': destination,
'is_analyzed': isAnalyzed,
'metadata': metadata,
'is_mature': isMature,
'account': account?.toJson(),
'account_id': accountId,
};
}

View File

@ -63,7 +63,7 @@ class Event {
class EventMessageBody {
String text;
String algorithm;
List<int>? attachments;
List<String>? attachments;
int? quoteEvent;
int? relatedEvent;
List<int>? relatedUsers;
@ -82,7 +82,7 @@ class EventMessageBody {
text: json['text'] ?? '',
algorithm: json['algorithm'] ?? 'plain',
attachments: json['attachments'] != null
? List<int>.from(json['attachments'].map((x) => x))
? List<String>.from(json['attachments']?.whereType<String>())
: null,
quoteEvent: json['quote_event'],
relatedEvent: json['related_event'],

65
lib/models/link.dart Normal file
View File

@ -0,0 +1,65 @@
class LinkMeta {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
String entryId;
String? icon;
String url;
String? title;
String? image;
String? video;
String? audio;
String? description;
String? siteName;
LinkMeta({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.entryId,
required this.icon,
required this.url,
required this.title,
required this.image,
required this.video,
required this.audio,
required this.description,
required this.siteName,
});
factory LinkMeta.fromJson(Map<String, dynamic> json) => LinkMeta(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
entryId: json['entry_id'],
icon: json['icon'],
url: json['url'],
title: json['title'],
image: json['image'],
video: json['video'],
audio: json['audio'],
description: json['description'],
siteName: json['site_name'],
);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'entry_id': entryId,
'icon': icon,
'url': url,
'title': title,
'image': image,
'video': video,
'audio': audio,
'description': description,
'site_name': siteName,
};
}

View File

@ -8,6 +8,8 @@ class Post {
DateTime updatedAt;
DateTime? editedAt;
DateTime? deletedAt;
String? alias;
String? areaAlias;
dynamic body;
List<Tag>? tags;
List<Category>? categories;
@ -33,6 +35,8 @@ class Post {
required this.updatedAt,
required this.editedAt,
required this.deletedAt,
required this.alias,
required this.areaAlias,
required this.type,
required this.body,
required this.tags,
@ -60,6 +64,8 @@ class Post {
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
alias: json['alias'],
areaAlias: json['area_alias'],
type: json['type'],
body: json['body'],
tags: json['tags']?.map((x) => Tag.fromJson(x)).toList().cast<Tag>(),
@ -101,6 +107,8 @@ class Post {
'updated_at': updatedAt.toIso8601String(),
'edited_at': editedAt?.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'alias': alias,
'area_alias': areaAlias,
'type': type,
'body': body,
'tags': tags,

View File

@ -36,7 +36,7 @@ class Sticker {
String get imageUrl => ServiceFinder.buildUrl(
'files',
'/attachments/$attachmentId',
'/attachments/${attachment.rid}',
);
factory Sticker.fromJson(Map<String, dynamic> json) => Sticker(

View File

@ -148,7 +148,7 @@ class AttachmentUploaderController extends GetxController {
Future<void> uploadAttachmentWithCallback(
Uint8List data,
String path,
String usage,
String pool,
Map<String, dynamic>? metadata,
Function(Attachment?) callback,
) async {
@ -158,7 +158,7 @@ class AttachmentUploaderController extends GetxController {
final result = await _rawUploadAttachment(
data,
path,
usage,
pool,
metadata,
onProgress: (progress) {
progressOfUpload.value = progress;
@ -171,7 +171,7 @@ class AttachmentUploaderController extends GetxController {
Future<Attachment?> uploadAttachment(
Uint8List data,
String path,
String usage,
String pool,
Map<String, dynamic>? metadata,
) async {
if (isUploading.value) throw Exception('uploading blocked');
@ -180,7 +180,7 @@ class AttachmentUploaderController extends GetxController {
final result = await _rawUploadAttachment(
data,
path,
usage,
pool,
metadata,
onProgress: (progress) {
progressOfUpload.value = progress;
@ -191,14 +191,14 @@ class AttachmentUploaderController extends GetxController {
}
Future<Attachment?> _rawUploadAttachment(
Uint8List data, String path, String usage, Map<String, dynamic>? metadata,
Uint8List data, String path, String pool, Map<String, dynamic>? metadata,
{Function(double)? onProgress, Function(dynamic err)? onError}) async {
final AttachmentProvider provider = Get.find();
try {
final result = await provider.createAttachment(
data,
path,
usage,
pool,
metadata,
onProgress: onProgress,
);

View File

@ -17,6 +17,10 @@ class ChatCallProvider extends GetxController {
RxBool isReady = false.obs;
RxBool isMounted = false.obs;
RxBool isInitialized = false.obs;
RxBool isBusy = false.obs;
RxString lastDuration = '00:00:00'.obs;
Timer? lastDurationUpdateTimer;
String? token;
String? endpoint;
@ -38,6 +42,34 @@ class ChatCallProvider extends GetxController {
RxList<ParticipantTrack> participantTracks = RxList.empty(growable: true);
Rx<ParticipantTrack?> focusTrack = Rx(null);
void _updateDuration() {
if (current.value == null) {
lastDuration.value = '00:00:00';
return;
}
Duration duration = DateTime.now().difference(current.value!.createdAt);
String twoDigits(int n) => n.toString().padLeft(2, '0');
String formattedTime = '${twoDigits(duration.inHours)}:'
'${twoDigits(duration.inMinutes.remainder(60))}:'
'${twoDigits(duration.inSeconds.remainder(60))}';
lastDuration.value = formattedTime;
}
void enableDurationUpdater() {
_updateDuration();
lastDurationUpdateTimer = Timer.periodic(
const Duration(seconds: 1),
(_) => _updateDuration(),
);
}
void disableDurationUpdater() {
lastDurationUpdateTimer?.cancel();
lastDurationUpdateTimer = null;
}
Future<void> checkPermissions() async {
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) {
return;
@ -88,7 +120,30 @@ class ChatCallProvider extends GetxController {
void initRoom() {
initHardware();
room = Room();
room = Room(
roomOptions: const RoomOptions(
dynacast: true,
adaptiveStream: true,
defaultAudioPublishOptions: AudioPublishOptions(
name: 'call_voice',
stream: 'call_stream',
),
defaultVideoPublishOptions: VideoPublishOptions(
name: 'call_video',
stream: 'call_stream',
simulcast: true,
backupVideoCodec: BackupVideoCodec(enabled: true),
),
defaultScreenShareCaptureOptions: ScreenShareCaptureOptions(
useiOSBroadcastExtension: true,
params: VideoParametersPresets.screenShareH1080FPS30,
),
defaultCameraCaptureOptions: CameraCaptureOptions(
maxFrameRate: 30,
params: VideoParametersPresets.h1080_169,
),
),
);
listener = room.createListener();
WakelockPlus.enable();
}
@ -96,36 +151,12 @@ class ChatCallProvider extends GetxController {
void joinRoom(String url, String token) async {
if (isMounted.value) {
return;
} else {
isMounted.value = true;
}
try {
await room.connect(
url,
token,
roomOptions: const RoomOptions(
dynacast: true,
adaptiveStream: true,
defaultAudioPublishOptions: AudioPublishOptions(
name: 'call_voice',
stream: 'call_stream',
),
defaultVideoPublishOptions: VideoPublishOptions(
name: 'call_video',
stream: 'call_stream',
simulcast: true,
backupVideoCodec: BackupVideoCodec(enabled: true),
),
defaultScreenShareCaptureOptions: ScreenShareCaptureOptions(
useiOSBroadcastExtension: true,
params: VideoParametersPresets.screenShareH1080FPS30,
),
defaultCameraCaptureOptions: CameraCaptureOptions(
maxFrameRate: 30,
params: VideoParametersPresets.h1080_169,
),
),
fastConnectOptions: FastConnectOptions(
microphone: TrackOption(track: audioTrack.value),
camera: TrackOption(track: videoTrack.value),
@ -133,6 +164,8 @@ class ChatCallProvider extends GetxController {
);
} catch (e) {
rethrow;
} finally {
isMounted.value = true;
}
}
@ -152,7 +185,7 @@ class ChatCallProvider extends GetxController {
void onRoomDidUpdate() => sortParticipants();
void setupRoom() {
if(isInitialized.value) return;
if (isInitialized.value) return;
sortParticipants();
room.addListener(onRoomDidUpdate);
@ -164,6 +197,7 @@ class ChatCallProvider extends GetxController {
Hardware.instance.setSpeakerphoneOn(true);
}
isBusy.value = false;
isInitialized.value = true;
}
@ -366,6 +400,7 @@ class ChatCallProvider extends GetxController {
}
void disposeRoom() {
isBusy.value = false;
isMounted.value = false;
isInitialized.value = false;
current.value = null;

View File

@ -20,22 +20,22 @@ class AttachmentProvider extends GetConnect {
httpClient.baseUrl = ServiceFinder.buildUrl('files', null);
}
final Map<int, Attachment> _cachedResponses = {};
final Map<String, Attachment> _cachedResponses = {};
Future<List<Attachment?>> listMetadata(
List<int> id, {
List<String> rid, {
noCache = false,
}) async {
if (id.isEmpty) return List.empty();
if (rid.isEmpty) return List.empty();
List<Attachment?> result = List.filled(id.length, null);
List<int> pendingQuery = List.empty(growable: true);
List<Attachment?> result = List.filled(rid.length, null);
List<String> pendingQuery = List.empty(growable: true);
if (!noCache) {
for (var idx = 0; idx < id.length; idx++) {
if (_cachedResponses.containsKey(id[idx])) {
result[idx] = _cachedResponses[id[idx]];
for (var idx = 0; idx < rid.length; idx++) {
if (_cachedResponses.containsKey(rid[idx])) {
result[idx] = _cachedResponses[rid[idx]];
} else {
pendingQuery.add(id[idx]);
pendingQuery.add(rid[idx]);
}
}
}
@ -52,12 +52,12 @@ class AttachmentProvider extends GetConnect {
rawOut.data!.map((x) => Attachment.fromJson(x)).toList();
for (final item in out) {
if (item.destination != 0 && item.isAnalyzed) {
_cachedResponses[item.id] = item;
_cachedResponses[item.rid] = item;
}
}
for (var i = 0; i < out.length; i++) {
for (var j = 0; j < id.length; j++) {
if (out[i].id == id[j]) {
for (var j = 0; j < rid.length; j++) {
if (out[i].rid == rid[j]) {
result[j] = out[i];
}
}
@ -66,16 +66,16 @@ class AttachmentProvider extends GetConnect {
return result;
}
Future<Attachment?> getMetadata(int id, {noCache = false}) async {
if (!noCache && _cachedResponses.containsKey(id)) {
return _cachedResponses[id]!;
Future<Attachment?> getMetadata(String rid, {noCache = false}) async {
if (!noCache && _cachedResponses.containsKey(rid)) {
return _cachedResponses[rid]!;
}
final resp = await get('/attachments/$id/meta');
final resp = await get('/attachments/$rid/meta');
if (resp.statusCode == 200) {
final result = Attachment.fromJson(resp.body);
if (result.destination != 0 && result.isAnalyzed) {
_cachedResponses[id] = result;
_cachedResponses[rid] = result;
}
return result;
}
@ -84,7 +84,7 @@ class AttachmentProvider extends GetConnect {
}
Future<Attachment> createAttachment(
Uint8List data, String path, String usage, Map<String, dynamic>? metadata,
Uint8List data, String path, String pool, Map<String, dynamic>? metadata,
{Function(double)? onProgress}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
@ -108,7 +108,7 @@ class AttachmentProvider extends GetConnect {
final payload = dio.FormData.fromMap({
'alt': fileAlt,
'file': filePayload,
'usage': usage,
'pool': pool,
if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
'metadata': jsonEncode(metadata),
});
@ -133,8 +133,7 @@ class AttachmentProvider extends GetConnect {
Future<Response> updateAttachment(
int id,
String alt,
String usage, {
String alt, {
bool isMature = false,
}) async {
final AuthProvider auth = Get.find();
@ -144,7 +143,6 @@ class AttachmentProvider extends GetConnect {
var resp = await client.put('/attachments/$id', {
'alt': alt,
'usage': usage,
'is_mature': isMature,
});
@ -169,7 +167,7 @@ class AttachmentProvider extends GetConnect {
return resp;
}
void clearCache({int? id}) {
void clearCache({String? id}) {
if (id != null) {
_cachedResponses.remove(id);
} else {

View File

@ -9,13 +9,20 @@ class PostProvider extends GetConnect {
}
Future<Response> listRecommendations(int page,
{int? realm, String? channel}) async {
{String? realm, String? channel}) async {
GetConnect client;
final AuthProvider auth = Get.find();
final queries = [
'take=${10}',
'offset=$page',
if (realm != null) 'realmId=$realm',
if (realm != null) 'realm=$realm',
];
final resp = await get(
if (auth.isAuthorized.value) {
client = auth.configureClient('co');
} else {
client = ServiceFinder.configureClient('co');
}
final resp = await client.get(
channel == null
? '/recommendations?${queries.join('&')}'
: '/recommendations/$channel?${queries.join('&')}',
@ -45,14 +52,14 @@ class PostProvider extends GetConnect {
}
Future<Response> listPost(int page,
{int? realm, String? author, tag, category}) async {
{String? realm, String? author, tag, category}) async {
final queries = [
'take=${10}',
'offset=$page',
if (tag != null) 'tag=$tag',
if (category != null) 'category=$category',
if (author != null) 'author=$author',
if (realm != null) 'realmId=$realm',
if (realm != null) 'realm=$realm',
];
final resp = await get('/posts?${queries.join('&')}');
if (resp.statusCode != 200) {

View File

@ -0,0 +1,25 @@
import 'dart:convert';
import 'dart:developer';
import 'package:get/get.dart';
import 'package:solian/models/link.dart';
import 'package:solian/services.dart';
class LinkExpandController extends GetxController {
final Map<String, LinkMeta?> _cachedResponse = {};
Future<LinkMeta?> expandLink(String url) async {
final target = utf8.fuse(base64).encode(url);
if (_cachedResponse.containsKey(target)) return _cachedResponse[target];
final client = ServiceFinder.configureClient('dealer');
final resp = await client.get('/api/links/$target');
if (resp.statusCode != 200) {
log('Unable to expand link ($url), status: ${resp.statusCode}, response: ${resp.body}');
_cachedResponse[target] = null;
return null;
}
final result = LinkMeta.fromJson(resp.body);
_cachedResponse[target] = result;
return result;
}
}

View File

@ -116,7 +116,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
attachResult = await provider.createAttachment(
await file.readAsBytes(),
file.path,
'p.$position',
'avatar',
null,
);
} catch (e) {

View File

@ -66,7 +66,7 @@ class _StickerScreenState extends State<StickerScreen> {
Widget _buildEmoteEntry(Sticker item, String prefix) {
final imageUrl = ServiceFinder.buildUrl(
'files',
'/attachments/${item.attachmentId}',
'/attachments/${item.attachment.rid}',
);
return ListTile(
title: Text(item.name),

View File

@ -12,16 +12,15 @@ import 'package:solian/widgets/chat/call/call_participant.dart';
import 'package:livekit_client/livekit_client.dart' as livekit;
class CallScreen extends StatefulWidget {
const CallScreen({super.key});
final bool hideAppBar;
const CallScreen({super.key, this.hideAppBar = false});
@override
State<CallScreen> createState() => _CallScreenState();
}
class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
Timer? _timer;
String _currentDuration = '00:00:00';
int _layoutMode = 0;
bool _showControls = true;
@ -37,26 +36,6 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
curve: Curves.fastOutSlowIn,
);
String _parseDuration() {
final ChatCallProvider provider = Get.find();
if (provider.current.value == null) return '00:00:00';
Duration duration =
DateTime.now().difference(provider.current.value!.createdAt);
String twoDigits(int n) => n.toString().padLeft(2, '0');
String formattedTime = '${twoDigits(duration.inHours)}:'
'${twoDigits(duration.inMinutes.remainder(60))}:'
'${twoDigits(duration.inSeconds.remainder(60))}';
return formattedTime;
}
void _updateDuration() {
setState(() {
_currentDuration = _parseDuration();
});
}
void _switchLayout() {
if (_layoutMode < 1) {
setState(() => _layoutMode++);
@ -191,15 +170,15 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
@override
void initState() {
Get.find<ChatCallProvider>().setupRoom();
super.initState();
_updateDuration();
_planAutoHideControls();
_timer = Timer.periodic(
const Duration(seconds: 1),
(_) => _updateDuration(),
);
Future.delayed(Duration.zero, () {
Get.find<ChatCallProvider>()
..setupRoom()
..enableDurationUpdater();
_planAutoHideControls();
});
}
@override
@ -210,30 +189,34 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final ChatCallProvider provider = Get.find();
final ChatCallProvider ctrl = Get.find();
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context),
centerTitle: true,
toolbarHeight: SolianTheme.toolbarHeight(context),
title: RichText(
textAlign: TextAlign.center,
text: TextSpan(children: [
TextSpan(
text: 'call'.tr,
style: Theme.of(context).textTheme.titleLarge,
appBar: widget.hideAppBar
? null
: AppBar(
leading: AppBarLeadingButton.adaptive(context),
centerTitle: true,
toolbarHeight: SolianTheme.toolbarHeight(context),
title: Obx(
() => RichText(
textAlign: TextAlign.center,
text: TextSpan(children: [
TextSpan(
text: 'call'.tr,
style: Theme.of(context).textTheme.titleLarge,
),
const TextSpan(text: '\n'),
TextSpan(
text: ctrl.lastDuration.value,
style: Theme.of(context).textTheme.bodySmall,
),
]),
),
),
),
const TextSpan(text: '\n'),
TextSpan(
text: _currentDuration,
style: Theme.of(context).textTheme.bodySmall,
),
]),
),
),
body: SafeArea(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
@ -259,13 +242,21 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(call.room.serverRegion ?? 'unknown'),
const SizedBox(width: 6),
Text(call.room.serverVersion ?? 'unknown')
],
),
Obx(() {
return Row(
children: [
Text(
call.channel.value?.name ??
'unknown'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 6),
Text(call.lastDuration.value)
],
);
}),
Row(
children: [
Text(
@ -332,7 +323,6 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
Expanded(
child: Material(
color: Theme.of(context).colorScheme.surfaceContainerLow,
elevation: 2,
child: Builder(
builder: (context) {
switch (_layoutMode) {
@ -345,15 +335,15 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
),
),
),
if (provider.room.localParticipant != null)
if (ctrl.room.localParticipant != null)
SizeTransition(
sizeFactor: _controlsAnimation,
axis: Axis.vertical,
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: ControlsWidget(
provider.room,
provider.room.localParticipant!,
ctrl.room,
ctrl.room.localParticipant!,
),
),
),
@ -370,17 +360,13 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
@override
void deactivate() {
_timer?.cancel();
_timer = null;
Get.find<ChatCallProvider>().disableDurationUpdater();
super.deactivate();
}
@override
void activate() {
_timer ??= Timer.periodic(
const Duration(seconds: 1),
(_) => _updateDuration(),
);
Get.find<ChatCallProvider>().enableDurationUpdater();
super.activate();
}
}

View File

@ -11,9 +11,11 @@ import 'package:solian/models/channel.dart';
import 'package:solian/models/event.dart';
import 'package:solian/models/packet.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/call.dart';
import 'package:solian/providers/content/channel.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/channel/call/call.dart';
import 'package:solian/screens/channel/channel_detail.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart';
@ -39,7 +41,7 @@ class ChannelChatScreen extends StatefulWidget {
}
class _ChannelChatScreenState extends State<ChannelChatScreen>
with WidgetsBindingObserver {
with WidgetsBindingObserver, TickerProviderStateMixin {
DateTime? _isOutOfSyncSince;
bool _isBusy = false;
@ -238,77 +240,104 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
);
}
return Column(
return Row(
children: [
if (_ongoingCall != null)
ChannelCallIndicator(
channel: _channel!,
ongoingCall: _ongoingCall!,
),
Expanded(
child: ChatEventList(
scope: widget.realm,
channel: _channel!,
chatController: _chatController,
onEdit: (item) {
setState(() => _messageToEditing = item);
},
onReply: (item) {
setState(() => _messageToReplying = item);
},
),
),
if (_isOutOfSyncSince != null)
ListTile(
contentPadding: const EdgeInsets.only(left: 16, right: 8),
tileColor: Theme.of(context).colorScheme.surfaceContainerLow,
leading: const Icon(Icons.history_toggle_off),
title: Text('messageOutOfSync'.tr),
subtitle: Text('messageOutOfSyncCaption'.tr),
trailing: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
setState(() => _isOutOfSyncSince = null);
},
),
onTap: _isBusy
? null
: () {
_keepUpdateWithServer();
child: Column(
children: [
if (_ongoingCall != null)
ChannelCallIndicator(
channel: _channel!,
ongoingCall: _ongoingCall!,
onJoin: () {
if (!SolianTheme.isLargeScreen(context)) {
final ChatCallProvider call = Get.find();
call.gotoScreen(context);
}
},
),
Obx(() {
if (_chatController.isLoading.isTrue) {
return const LinearProgressIndicator().animate().slideY();
} else {
return const SizedBox();
}
}),
ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: SafeArea(
child: ChatMessageInput(
edit: _messageToEditing,
reply: _messageToReplying,
realm: widget.realm,
placeholder: placeholder,
channel: _channel!,
onSent: (Event item) {
setState(() {
_chatController.addPendingEvent(item);
});
},
onReset: () {
setState(() {
_messageToReplying = null;
_messageToEditing = null;
});
},
),
Expanded(
child: ChatEventList(
scope: widget.realm,
channel: _channel!,
chatController: _chatController,
onEdit: (item) {
setState(() => _messageToEditing = item);
},
onReply: (item) {
setState(() => _messageToReplying = item);
},
),
),
),
if (_isOutOfSyncSince != null)
ListTile(
contentPadding: const EdgeInsets.only(left: 16, right: 8),
tileColor:
Theme.of(context).colorScheme.surfaceContainerLow,
leading: const Icon(Icons.history_toggle_off),
title: Text('messageOutOfSync'.tr),
subtitle: Text('messageOutOfSyncCaption'.tr),
trailing: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
setState(() => _isOutOfSyncSince = null);
},
),
onTap: _isBusy
? null
: () {
_keepUpdateWithServer();
},
),
Obx(() {
if (_chatController.isLoading.isTrue) {
return const LinearProgressIndicator().animate().slideY();
} else {
return const SizedBox();
}
}),
ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: SafeArea(
child: ChatMessageInput(
edit: _messageToEditing,
reply: _messageToReplying,
realm: widget.realm,
placeholder: placeholder,
channel: _channel!,
onSent: (Event item) {
setState(() {
_chatController.addPendingEvent(item);
});
},
onReset: () {
setState(() {
_messageToReplying = null;
_messageToEditing = null;
});
},
),
),
),
),
],
),
),
Obx(() {
final ChatCallProvider call = Get.find();
if (call.isMounted.value && SolianTheme.isLargeScreen(context)) {
return const Expanded(
child: Row(children: [
VerticalDivider(width: 0.3, thickness: 0.3),
Expanded(
child: CallScreen(hideAppBar: true),
),
]),
);
}
return const SizedBox();
}),
],
);
}),

View File

@ -5,6 +5,7 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/account/signin_required_overlay.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/app_bar_leading.dart';
@ -27,20 +28,18 @@ class _HomeScreenState extends State<HomeScreen>
void initState() {
super.initState();
_postController = PostListController();
_tabController = TabController(length: 2, vsync: this);
_tabController = TabController(length: 3, vsync: this);
_tabController.addListener(() {
switch (_tabController.index) {
case 0:
case 1:
if (_postController.mode.value == _tabController.index) return;
_postController.mode.value = _tabController.index;
_postController.reloadAllOver();
}
if (_postController.mode.value == _tabController.index) return;
_postController.mode.value = _tabController.index;
_postController.reloadAllOver();
});
}
@override
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
@ -82,6 +81,7 @@ class _HomeScreenState extends State<HomeScreen>
controller: _tabController,
tabs: [
Tab(text: 'postListNews'.tr),
Tab(text: 'postListFriends'.tr),
Tab(text: 'postListShuffle'.tr),
],
),
@ -108,6 +108,23 @@ class _HomeScreenState extends State<HomeScreen>
),
]),
),
Obx(() {
if (auth.isAuthorized.value) {
return RefreshIndicator(
onRefresh: () => _postController.reloadAllOver(),
child: CustomScrollView(slivers: [
PostWarpedListWidget(
controller: _postController.pagingController,
onUpdate: () => _postController.reloadAllOver(),
),
]),
);
} else {
return SigninRequiredOverlay(
onSignedIn: () => _postController.reloadAllOver(),
);
}
}),
PostShuffleSwiper(controller: _postController),
],
);

View File

@ -65,7 +65,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
final AttachmentUploaderController uploader = Get.find();
if (uploader.queueOfUpload.any(
((x) => x.usage == 'i.attachment' && x.isUploading),
((x) => x.isUploading),
)) {
context.showErrorDialog('attachmentUploadInProgress'.tr);
return;
@ -90,8 +90,8 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString);
} else {
_editorController.localClear();
_editorController.currentClear();
_editorController.localClear();
AppRouter.instance.pop(resp.body);
}
@ -176,10 +176,19 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
children: [
ListTile(
tileColor: Theme.of(context).colorScheme.surfaceContainerLow,
title: Text(
_editorController.title ?? 'title'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
title: Row(
children: [
Text(
_editorController.title ?? 'title'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 6),
if (_editorController.aliasController.text.isNotEmpty)
Badge(
label: Text('#${_editorController.aliasController.text}'),
),
],
),
subtitle: Text(
_editorController.description ?? 'description'.tr,
@ -255,42 +264,106 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
),
],
),
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView(
child: Column(
children: [
if (_isBusy)
const LinearProgressIndicator().animate().scaleX(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: TextField(
maxLines: null,
autofocus: true,
autocorrect: true,
keyboardType: TextInputType.multiline,
controller: _editorController.contentController,
focusNode: _contentFocusNode,
decoration: InputDecoration.collapsed(
hintText: 'postContentPlaceholder'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
Expanded(
child: ListView(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: TextField(
maxLines: null,
autofocus: true,
autocorrect: true,
keyboardType: TextInputType.multiline,
controller:
_editorController.contentController,
focusNode: _contentFocusNode,
decoration: InputDecoration.collapsed(
hintText: 'postContentPlaceholder'.tr,
),
onTapOutside: (_) => FocusManager
.instance.primaryFocus
?.unfocus(),
),
),
const SizedBox(height: 120)
],
),
),
const SizedBox(height: 120)
Obx(() {
final textStyle = TextStyle(
fontSize: 12,
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.75),
);
final showFactors = [
_editorController.isRestoreFromLocal.value,
_editorController.lastSaveTime.value != null,
];
final doShow = showFactors.any((x) => x);
return Container(
padding: const EdgeInsets.symmetric(
vertical: 4,
horizontal: 16,
),
child: Row(
children: [
if (showFactors[0])
Text('postRestoreFromLocal'.tr,
style: textStyle)
.paddingOnly(right: 4),
if (showFactors[0])
InkWell(
child: Text('clear'.tr, style: textStyle),
onTap: () {
_editorController.localClear();
_editorController.currentClear();
setState(() {});
},
),
if (showFactors.where((x) => x).length > 1)
Text(
'·',
style: textStyle,
).paddingSymmetric(horizontal: 8),
if (showFactors[1])
Text(
'postAutoSaveAt'.trParams({
'date': DateFormat('HH:mm:ss').format(
_editorController.lastSaveTime.value ??
DateTime.now(),
)
}),
style: textStyle,
),
],
),
)
.animate(
key: const Key('post-editor-hint-animation'),
target: doShow ? 1 : 0,
)
.fade(curve: Curves.easeInOut, duration: 300.ms);
}),
],
),
),
if (SolianTheme.isLargeScreen(context))
const VerticalDivider(width: 0.3, thickness: 0.3)
.paddingSymmetric(
horizontal: 8,
horizontal: 16,
),
if (SolianTheme.isLargeScreen(context))
Expanded(
@ -298,7 +371,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
child: MarkdownTextContent(
content: _editorController.contentController.text,
parentId: 'post-editor-preview',
).paddingOnly(top: 8),
).paddingOnly(top: 12, right: 16),
),
),
],
@ -309,85 +382,39 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() {
final textStyle = TextStyle(
fontSize: 12,
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.75),
);
final showFactors = [
_editorController.isRestoreFromLocal.value,
_editorController.lastSaveTime.value != null,
];
final doShow = showFactors.any((x) => x);
return Container(
padding: const EdgeInsets.symmetric(
vertical: 4,
horizontal: 16,
),
child: Row(
children: [
if (showFactors[0])
Text('postRestoreFromLocal'.tr, style: textStyle)
.paddingOnly(right: 4),
if (showFactors[0])
InkWell(
child: Text('clear'.tr, style: textStyle),
onTap: () {
_editorController.localClear();
_editorController.currentClear();
setState(() {});
},
),
if (showFactors.where((x) => x).length > 1)
Text(
'·',
style: textStyle,
).paddingSymmetric(horizontal: 8),
if (showFactors[1])
Text(
'postAutoSaveAt'.trParams({
'date': DateFormat('HH:mm:ss').format(
_editorController.lastSaveTime.value ??
DateTime.now(),
)
}),
style: textStyle,
),
],
),
)
.animate(
key: const Key('post-editor-hint-animation'),
target: doShow ? 1 : 0,
)
.fade(curve: Curves.easeInOut, duration: 300.ms);
}),
if (_editorController.mode.value == 0)
Obx(
() => TweenAnimationBuilder<double>(
tween: Tween(
begin: 0,
end: _editorController.contentLength.value / 4096,
),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
builder: (context, value, _) => LinearProgressIndicator(
minHeight: 2,
color: _editorController.contentLength.value > 4096
? Colors.red[900]
: Theme.of(context).colorScheme.primary,
value: value,
),
),
),
const Divider(thickness: 0.3, height: 0.3),
SizedBox(
height: 56,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
if (_editorController.mode.value == 0)
Obx(
() => TweenAnimationBuilder<double>(
tween: Tween(
begin: 0,
end: _editorController.contentLength.value /
4096,
),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
builder: (context, value, _) => SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
backgroundColor: Theme.of(context)
.colorScheme
.secondaryContainer,
color: _editorController.contentLength.value >
4096
? Colors.red[900]
: Theme.of(context).colorScheme.primary,
value: value,
),
).paddingAll(10),
),
).paddingSymmetric(horizontal: 4),
Obx(() {
final isDraft = _editorController.isDraft.value;
return IconButton(

View File

@ -171,7 +171,7 @@ class _RealmPostListWidgetState extends State<RealmPostListWidget> {
Response resp;
try {
resp = await provider.listPost(pageKey, realm: widget.realm.id);
resp = await provider.listPost(pageKey, realm: widget.realm.alias);
} catch (e) {
_pagingController.error = e;
return;

View File

@ -1,3 +1,4 @@
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:solian/theme.dart';
@ -29,6 +30,15 @@ class RootShell extends StatelessWidget {
Widget build(BuildContext context) {
final routeName = state.topRoute?.name;
if (routeName != null) {
FirebaseAnalytics.instance.logEvent(
name: 'screen_view',
parameters: {
'firebase_screen': routeName,
},
);
}
return Scaffold(
key: rootScaffoldKey,
drawer: SolianTheme.isLargeScreen(context)

View File

@ -13,6 +13,7 @@ const i18nEnglish = {
'more': 'More',
'share': 'Share',
'shareNoUri': 'Share text content',
'alias': 'Alias',
'feed': 'Feed',
'unlink': 'Unlink',
'feedSearch': 'Search Feed',
@ -124,6 +125,7 @@ const i18nEnglish = {
'postThumbnailAttachment': 'Attachment serial number',
'postPinned': 'Pinned',
'postListNews': 'News',
'postListFriends': 'Friends',
'postListShuffle': 'Random',
'postEditorModeStory': 'Post a post',
'postEditorModeArticle': 'Post an article',
@ -267,6 +269,7 @@ const i18nEnglish = {
'callOngoing': 'A call is ongoing...',
'callOngoingEmpty': 'A call is on hold...',
'callOngoingParticipants': '@count people are calling...',
'callOngoingJoined': 'Call last @duration',
'callJoin': 'Join',
'callResume': 'Resume',
'callMicrophone': 'Microphone',
@ -377,4 +380,5 @@ const i18nEnglish = {
'messageOutOfSyncCaption':
'Since the App has entered the background, there may be a time difference between the message list and the server. Click to Refresh.',
'messageHistoryWipe': 'Wipe local message history',
'unknown': 'Unknown',
};

View File

@ -21,6 +21,7 @@ const i18nSimplifiedChinese = {
'more': '更多',
'share': '分享',
'shareNoUri': '分享文字内容',
'alias': '别名',
'feed': '资讯',
'unlink': '移除链接',
'feedSearch': '搜索资讯',
@ -124,6 +125,7 @@ const i18nSimplifiedChinese = {
'articleDetail': '文章详情',
'draftBoxOpen': '打开草稿箱',
'postListNews': '新鲜事',
'postListFriends': '好友圈',
'postListShuffle': '打乱看',
'postNew': '创建新帖子',
'postNewInRealmHint': '在领域 @realm 里发表新帖子',
@ -245,6 +247,7 @@ const i18nSimplifiedChinese = {
'callOngoing': '一则通话正在进行中…',
'callOngoingEmpty': '一则通话待机中…',
'callOngoingParticipants': '@count 人正在进行通话…',
'callOngoingJoined': '通话进行 @duration',
'callJoin': '加入',
'callResume': '恢复',
'callMicrophone': '麦克风',
@ -343,4 +346,5 @@ const i18nSimplifiedChinese = {
'messageOutOfSync': '消息可能与服务器脱节',
'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。',
'messageHistoryWipe': '清除消息记录',
'unknown': '未知',
};

View File

@ -24,7 +24,6 @@ class AccountAvatar extends StatelessWidget {
if (content is String) {
direct = content.startsWith('http');
if (!isEmpty) isEmpty = content.isEmpty;
if (!isEmpty) isEmpty = content.endsWith('/attachments/0');
}
final url = direct

View File

@ -16,10 +16,12 @@ class AttachmentAttrEditorDialog extends StatefulWidget {
});
@override
State<AttachmentAttrEditorDialog> createState() => _AttachmentAttrEditorDialogState();
State<AttachmentAttrEditorDialog> createState() =>
_AttachmentAttrEditorDialogState();
}
class _AttachmentAttrEditorDialogState extends State<AttachmentAttrEditorDialog> {
class _AttachmentAttrEditorDialogState
extends State<AttachmentAttrEditorDialog> {
final _altController = TextEditingController();
bool _isBusy = false;
@ -33,11 +35,10 @@ class _AttachmentAttrEditorDialogState extends State<AttachmentAttrEditorDialog>
final resp = await provider.updateAttachment(
widget.item.id,
_altController.value.text,
widget.item.usage,
isMature: _isMature,
);
Get.find<AttachmentProvider>().clearCache(id: widget.item.id);
Get.find<AttachmentProvider>().clearCache(id: widget.item.rid);
setState(() => _isBusy = false);
return Attachment.fromJson(resp.body);
@ -109,7 +110,7 @@ class _AttachmentAttrEditorDialogState extends State<AttachmentAttrEditorDialog>
TextButton(
style: TextButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onSurfaceVariant),
Theme.of(context).colorScheme.onSurfaceVariant),
onPressed: () => Navigator.pop(context),
child: Text('cancel'.tr),
),

View File

@ -22,19 +22,19 @@ import 'package:solian/widgets/attachments/attachment_attr_editor.dart';
import 'package:solian/widgets/attachments/attachment_fullscreen.dart';
class AttachmentEditorPopup extends StatefulWidget {
final String usage;
final String pool;
final bool singleMode;
final bool imageOnly;
final bool autoUpload;
final double? imageMaxWidth;
final double? imageMaxHeight;
final List<int>? initialAttachments;
final void Function(int) onAdd;
final void Function(int) onRemove;
final List<String>? initialAttachments;
final void Function(String) onAdd;
final void Function(String) onRemove;
const AttachmentEditorPopup({
super.key,
required this.usage,
required this.pool,
required this.onAdd,
required this.onRemove,
this.singleMode = false,
@ -73,7 +73,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
_enqueueTaskBatch(medias.map((x) {
final file = File(x.path);
return AttachmentUploadTask(file: file, usage: widget.usage);
return AttachmentUploadTask(file: file, usage: widget.pool);
}));
} else {
final media = await _imagePicker.pickMedia(
@ -83,7 +83,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
if (media == null) return;
_enqueueTask(
AttachmentUploadTask(file: File(media.path), usage: widget.usage),
AttachmentUploadTask(file: File(media.path), usage: widget.pool),
);
}
}
@ -97,7 +97,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
final file = File(media.path);
_enqueueTask(
AttachmentUploadTask(file: file, usage: widget.usage),
AttachmentUploadTask(file: file, usage: widget.pool),
);
}
@ -113,7 +113,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
List<File> files = result.paths.map((path) => File(path!)).toList();
_enqueueTaskBatch(files.map((x) {
return AttachmentUploadTask(file: x, usage: widget.usage);
return AttachmentUploadTask(file: x, usage: widget.pool);
}));
}
@ -131,7 +131,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
final file = File(media.path);
_enqueueTask(
AttachmentUploadTask(file: file, usage: widget.usage),
AttachmentUploadTask(file: file, usage: widget.pool),
);
}
@ -181,13 +181,11 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
WidgetsBinding.instance.addPostFrameCallback((_) => controller.dispose());
if (input == null || input.isEmpty) return;
final value = int.tryParse(input);
if (value == null) return;
final AttachmentProvider attach = Get.find();
final result = await attach.getMetadata(value);
final result = await attach.getMetadata(input);
if (result != null) {
widget.onAdd(result.id);
widget.onAdd(result.rid);
setState(() => _attachments.add(result));
if (widget.singleMode) Navigator.pop(context);
}
@ -202,11 +200,11 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
_uploadController.uploadAttachmentWithCallback(
data,
'Pasted Image',
widget.usage,
widget.pool,
null,
(item) {
if (item == null) return;
widget.onAdd(item.id);
widget.onAdd(item.rid);
if (mounted) {
setState(() => _attachments.add(item));
if (widget.singleMode) Navigator.pop(context);
@ -254,7 +252,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
.listMetadata(widget.initialAttachments ?? List.empty())
.then((result) {
setState(() {
_attachments = result;
_attachments = List.from(result, growable: true);
_isBusy = false;
_isFirstTimeBusy = false;
});
@ -413,11 +411,11 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
: () {
_uploadController
.performSingleTask(index)
.then((r) {
if (r == null) return;
widget.onAdd(r.id);
.then((out) {
if (out == null) return;
widget.onAdd(out.rid);
if (mounted) {
setState(() => _attachments.add(r));
setState(() => _attachments.add(out));
if (widget.singleMode) {
Navigator.pop(context);
}
@ -515,7 +513,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
onTap: () {
_deleteAttachment(element).then((_) {
widget.onRemove(element.id);
widget.onRemove(element.rid);
setState(() => _attachments.removeAt(index));
});
},
@ -529,7 +527,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
),
onTap: () {
widget.onRemove(element.id);
widget.onRemove(element.rid);
setState(() => _attachments.removeAt(index));
},
),
@ -560,7 +558,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
void _startUploading() {
_uploadController.performUploadQueue(onData: (r) {
widget.onAdd(r.id);
widget.onAdd(r.rid);
if (mounted) {
setState(() => _attachments.add(r));
if (widget.singleMode) Navigator.pop(context);
@ -584,7 +582,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
if (_uploadController.isUploading.value) return;
_enqueueTaskBatch(detail.files.map((x) {
final file = File(x.path);
return AttachmentUploadTask(file: file, usage: widget.usage);
return AttachmentUploadTask(file: file, usage: widget.pool);
}));
},
child: Column(

View File

@ -67,7 +67,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
Future<void> _saveToAlbum() async {
final url = ServiceFinder.buildUrl(
'files',
'/attachments/${widget.item.id}',
'/attachments/${widget.item.rid}',
);
if (PlatformInfo.isWeb || PlatformInfo.isDesktop) {
@ -258,7 +258,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
spacing: 6,
children: [
Text(
'#${widget.item.id}',
'#${widget.item.rid}',
style: metaTextStyle,
),
if (widget.item.metadata?['width'] != null &&

View File

@ -15,6 +15,7 @@ class AttachmentItem extends StatefulWidget {
final Attachment item;
final bool showBadge;
final bool showHideButton;
final bool autoload;
final BoxFit fit;
final String? badge;
final Function? onHide;
@ -27,6 +28,7 @@ class AttachmentItem extends StatefulWidget {
this.fit = BoxFit.cover,
this.showBadge = true,
this.showHideButton = true,
this.autoload = false,
this.onHide,
});
@ -49,7 +51,10 @@ class _AttachmentItemState extends State<AttachmentItem> {
onHide: widget.onHide,
);
case 'video':
return _AttachmentItemVideo(item: widget.item);
return _AttachmentItemVideo(
item: widget.item,
autoload: widget.autoload,
);
default:
return Center(
child: Container(
@ -86,7 +91,7 @@ class _AttachmentItemState extends State<AttachmentItem> {
launchUrlString(
ServiceFinder.buildUrl(
'files',
'/attachments/${widget.item.id}',
'/attachments/${widget.item.rid}',
),
);
},
@ -130,7 +135,7 @@ class _AttachmentItemImage extends StatelessWidget {
fit: fit,
imageUrl: ServiceFinder.buildUrl(
'files',
'/attachments/${item.id}',
'/attachments/${item.rid}',
),
progressIndicatorBuilder: (context, url, downloadProgress) {
return Center(
@ -227,15 +232,18 @@ class _AttachmentItemVideo extends StatefulWidget {
class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
late final _player = Player(
configuration: const PlayerConfiguration(logLevel: MPVLogLevel.error),
configuration: const PlayerConfiguration(
logLevel: MPVLogLevel.error,
),
);
late final _controller = VideoController(_player);
bool _showContent = false;
void _startLoad() {
_player.open(
Media(ServiceFinder.buildUrl('files', '/attachments/${widget.item.id}')),
Future<void> _startLoad() async {
await _player.open(
Media(ServiceFinder.buildUrl('files', '/attachments/${widget.item.rid}')),
play: false,
);
setState(() => _showContent = true);
@ -244,7 +252,9 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
@override
void initState() {
super.initState();
_showContent = widget.autoload;
if (widget.autoload) {
_startLoad();
}
}
@override

View File

@ -1,4 +1,4 @@
import 'dart:math' show min;
import 'dart:math' as math;
import 'dart:ui';
import 'package:carousel_slider/carousel_slider.dart';
@ -14,10 +14,13 @@ import 'package:solian/widgets/sized_container.dart';
class AttachmentList extends StatefulWidget {
final String parentId;
final List<int> attachmentsId;
final List<String> attachmentsId;
final bool isGrid;
final bool isColumn;
final bool isForceGrid;
final bool autoload;
final double flatMaxHeight;
final double columnMaxWidth;
final double? width;
final double? viewport;
@ -27,8 +30,11 @@ class AttachmentList extends StatefulWidget {
required this.parentId,
required this.attachmentsId,
this.isGrid = false,
this.isColumn = false,
this.isForceGrid = false,
this.autoload = false,
this.flatMaxHeight = 720,
this.columnMaxWidth = 480,
this.width,
this.viewport,
});
@ -55,10 +61,12 @@ class _AttachmentListState extends State<AttachmentList> {
}
attach.listMetadata(widget.attachmentsId).then((result) {
setState(() {
_attachmentsMeta = result;
_isLoading = false;
});
if (mounted) {
setState(() {
_attachmentsMeta = result;
_isLoading = false;
});
}
_calculateAspectRatio();
});
}
@ -68,7 +76,8 @@ class _AttachmentListState extends State<AttachmentList> {
double? consistentValue;
int portrait = 0, square = 0, landscape = 0;
for (var entry in _attachmentsMeta) {
if (entry!.metadata?['ratio'] != null) {
if (entry == null) continue;
if (entry.metadata?['ratio'] != null) {
if (entry.metadata?['ratio'] is int) {
consistentValue ??= entry.metadata?['ratio'].toDouble();
} else {
@ -100,15 +109,17 @@ class _AttachmentListState extends State<AttachmentList> {
}
}
Widget _buildEntry(Attachment? element, int idx) {
Widget _buildEntry(Attachment? element, int idx, {double? width}) {
return AttachmentListEntry(
item: element,
parentId: widget.parentId,
width: widget.width,
width: width ?? widget.width,
badgeContent: '${idx + 1}/${_attachmentsMeta.length}',
showBadge: _attachmentsMeta.length > 1 && !widget.isGrid,
showBadge:
_attachmentsMeta.length > 1 && !widget.isGrid && !widget.isColumn,
showBorder: widget.attachmentsId.length > 1,
showMature: _showMature,
autoload: widget.autoload,
onReveal: (value) {
setState(() => _showMature = value);
},
@ -136,8 +147,46 @@ class _AttachmentListState extends State<AttachmentList> {
);
}
final isNotPureImage = _attachmentsMeta
.any((x) => x?.mimetype.split('/').firstOrNull != 'image');
if (widget.isColumn) {
var idx = 0;
const radius = BorderRadius.all(Radius.circular(8));
return Wrap(
spacing: 8,
runSpacing: 8,
children: widget.attachmentsId.map((x) {
final element = _attachmentsMeta[idx];
idx++;
if (element == null) return const SizedBox();
double ratio = element.metadata!['ratio']?.toDouble() ?? 16 / 9;
return Container(
constraints: BoxConstraints(
maxWidth: widget.columnMaxWidth,
maxHeight: 640,
),
child: AspectRatio(
aspectRatio: ratio,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
borderRadius: radius,
),
child: ClipRRect(
borderRadius: radius,
child: _buildEntry(element, idx),
),
),
),
);
}).toList(),
);
}
final isNotPureImage = _attachmentsMeta.any(
(x) => x?.mimetype.split('/').firstOrNull != 'image',
);
if (widget.isGrid && (widget.isForceGrid || !isNotPureImage)) {
const radius = BorderRadius.all(Radius.circular(8));
return GridView.builder(
@ -146,7 +195,7 @@ class _AttachmentListState extends State<AttachmentList> {
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: min(3, widget.attachmentsId.length),
crossAxisCount: math.min(3, widget.attachmentsId.length),
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
),
@ -155,8 +204,10 @@ class _AttachmentListState extends State<AttachmentList> {
final element = _attachmentsMeta[idx];
return Container(
decoration: BoxDecoration(
border:
Border.all(color: Theme.of(context).dividerColor, width: 1),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
borderRadius: radius,
),
child: ClipRRect(
@ -204,10 +255,12 @@ class AttachmentListEntry extends StatelessWidget {
final Attachment? item;
final String? badgeContent;
final double? width;
final double? height;
final bool showBorder;
final bool showBadge;
final bool showMature;
final bool isDense;
final bool autoload;
final Function(bool) onReveal;
const AttachmentListEntry({
@ -217,10 +270,12 @@ class AttachmentListEntry extends StatelessWidget {
this.item,
this.badgeContent,
this.width,
this.height,
this.showBorder = false,
this.showBadge = false,
this.showMature = false,
this.isDense = false,
this.autoload = false,
});
@override
@ -240,6 +295,7 @@ class AttachmentListEntry extends StatelessWidget {
return GestureDetector(
child: Container(
width: width ?? MediaQuery.of(context).size.width,
height: height,
decoration: BoxDecoration(
border: showBorder
? Border.symmetric(
@ -259,6 +315,7 @@ class AttachmentListEntry extends StatelessWidget {
item: item!,
badge: showBadge ? badgeContent : null,
showHideButton: !item!.isMature || showMature,
autoload: autoload,
onHide: () {
onReveal(false);
},
@ -323,13 +380,13 @@ class AttachmentListEntry extends StatelessWidget {
}
class AttachmentSelfContainedEntry extends StatefulWidget {
final int id;
final String rid;
final String parentId;
final bool isDense;
const AttachmentSelfContainedEntry({
super.key,
required this.id,
required this.rid,
required this.parentId,
this.isDense = false,
});
@ -348,10 +405,12 @@ class _AttachmentSelfContainedEntryState
final AttachmentProvider attachments = Get.find();
return FutureBuilder(
future: attachments.getMetadata(widget.id),
future: attachments.getMetadata(widget.rid),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Text('Loading...');
return const Center(
child: CircularProgressIndicator(),
);
}
return AttachmentListEntry(
@ -359,7 +418,6 @@ class _AttachmentSelfContainedEntryState
isDense: widget.isDense,
parentId: widget.parentId,
showMature: _showMature,
showBorder: true,
onReveal: (value) {
setState(() => _showMature = value);
},

View File

@ -9,14 +9,20 @@ import 'package:solian/models/call.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/call.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/chat/call/call_prejoin.dart';
class ChannelCallIndicator extends StatelessWidget {
final Channel channel;
final Call ongoingCall;
final Function onJoin;
const ChannelCallIndicator(
{super.key, required this.channel, required this.ongoingCall});
const ChannelCallIndicator({
super.key,
required this.channel,
required this.ongoingCall,
required this.onJoin,
});
void _showCallPrejoin(BuildContext context) {
showModalBottomSheet(
@ -25,6 +31,7 @@ class ChannelCallIndicator extends StatelessWidget {
builder: (context) => ChatCallPrejoinPopup(
ongoingCall: ongoingCall,
channel: channel,
onJoin: onJoin,
),
);
}
@ -40,48 +47,72 @@ class ChannelCallIndicator extends StatelessWidget {
dividerColor: Colors.transparent,
content: Row(
children: [
if (ongoingCall.participants.isEmpty) Text('callOngoingEmpty'.tr),
if (ongoingCall.participants.isNotEmpty)
Text('callOngoingParticipants'.trParams({
'count': ongoingCall.participants.length.toString(),
})),
Obx(() {
if (call.isInitialized.value) {
return Text('callOngoingJoined'.trParams({
'duration': call.lastDuration.value,
}));
} else if (ongoingCall.participants.isEmpty) {
return Text('callOngoingEmpty'.tr);
} else {
return Text('callOngoingParticipants'.trParams({
'count': ongoingCall.participants.length.toString(),
}));
}
}),
const SizedBox(width: 6),
if (ongoingCall.participants.isNotEmpty)
Container(
height: 28,
constraints: const BoxConstraints(maxWidth: 120),
child: AvatarStack(
Obx(() {
if (call.isInitialized.value) {
return const SizedBox();
}
if (ongoingCall.participants.isNotEmpty) {
return Container(
height: 28,
borderWidth: 0,
avatars: ongoingCall.participants.map((x) {
final userinfo = Account.fromJson(jsonDecode(x['metadata']));
return PlatformInfo.canCacheImage
? CachedNetworkImageProvider(userinfo.avatar)
as ImageProvider
: NetworkImage(userinfo.avatar) as ImageProvider;
}).toList(),
),
),
constraints: const BoxConstraints(maxWidth: 120),
child: AvatarStack(
height: 28,
borderWidth: 0,
avatars: ongoingCall.participants.map((x) {
final userinfo =
Account.fromJson(jsonDecode(x['metadata']));
return PlatformInfo.canCacheImage
? CachedNetworkImageProvider(userinfo.avatar)
as ImageProvider
: NetworkImage(userinfo.avatar) as ImageProvider;
}).toList(),
),
);
}
return const SizedBox();
})
],
),
actions: [
Obx(() {
if (call.current.value == null) {
if (call.isBusy.value) {
return const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 3),
).paddingAll(16);
} else if (call.current.value == null) {
return TextButton(
onPressed: () => _showCallPrejoin(context),
child: Text('callJoin'.tr),
);
} else if (call.channel.value?.id == channel.id) {
} else if (call.channel.value?.id == channel.id &&
!SolianTheme.isLargeScreen(context)) {
return TextButton(
onPressed: () => call.gotoScreen(context),
onPressed: () => onJoin(),
child: Text('callResume'.tr),
);
} else {
} else if (!SolianTheme.isLargeScreen(context)) {
return TextButton(
onPressed: null,
child: Text('callJoin'.tr),
);
}
return const SizedBox();
})
],
);

View File

@ -88,10 +88,12 @@ class _ControlsWidgetState extends State<ControlsWidget> {
void _disconnect() async {
if (await showDisconnectDialog() != true) return;
final ChatCallProvider provider = Get.find();
if (provider.current.value != null) {
provider.disposeRoom();
Navigator.pop(context);
final ChatCallProvider call = Get.find();
if (call.current.value != null) {
call.disposeRoom();
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
}
}
@ -209,8 +211,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
runSpacing: 5,
children: [
IconButton(
icon: Transform.flip(
flipX: true, child: const Icon(Icons.exit_to_app)),
icon: const Icon(Icons.exit_to_app),
color: Theme.of(context).colorScheme.onSurface,
onPressed: _disconnect,
),

View File

@ -11,11 +11,13 @@ import 'package:solian/providers/call.dart';
class ChatCallPrejoinPopup extends StatefulWidget {
final Call ongoingCall;
final Channel channel;
final Function onJoin;
const ChatCallPrejoinPopup({
super.key,
required this.ongoingCall,
required this.channel,
required this.onJoin,
});
@override
@ -25,22 +27,23 @@ class ChatCallPrejoinPopup extends StatefulWidget {
class _ChatCallPrejoinPopupState extends State<ChatCallPrejoinPopup> {
bool _isBusy = false;
void performJoin() async {
void _performJoin() async {
final AuthProvider auth = Get.find();
final ChatCallProvider provider = Get.find();
final ChatCallProvider call = Get.find();
if (auth.isAuthorized.isFalse) return;
setState(() => _isBusy = true);
provider.setCall(widget.ongoingCall, widget.channel);
call.setCall(widget.ongoingCall, widget.channel);
call.isBusy.value = true;
try {
final resp = await provider.getRoomToken();
final resp = await call.getRoomToken();
final token = resp.$1;
final endpoint = resp.$2;
provider.initRoom();
provider.setupRoomListeners(
call.initRoom();
call.setupRoomListeners(
onDisconnected: (reason) {
context.showSnackbar(
'callDisconnected'.trParams({'reason': reason.toString()}),
@ -48,23 +51,22 @@ class _ChatCallPrejoinPopupState extends State<ChatCallPrejoinPopup> {
},
);
provider.joinRoom(endpoint, token);
call.joinRoom(endpoint, token);
provider.gotoScreen(context).then((_) {
Navigator.pop(context);
});
Navigator.pop(context);
} catch (e) {
context.showErrorDialog(e);
}
widget.onJoin();
setState(() => _isBusy = false);
}
@override
void initState() {
final ChatCallProvider provider = Get.find();
provider.checkPermissions().then((_) {
provider.initHardware();
final ChatCallProvider call = Get.find();
call.checkPermissions().then((_) {
call.initHardware();
});
super.initState();
@ -169,7 +171,7 @@ class _ChatCallPrejoinPopupState extends State<ChatCallPrejoinPopup> {
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
),
onPressed: _isBusy ? null : performJoin,
onPressed: _isBusy ? null : _performJoin,
child: Text('callJoin'.tr),
),
],

View File

@ -7,10 +7,10 @@ class ChatCallCurrentIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ChatCallProvider provider = Get.find();
final ChatCallProvider call = Get.find();
return Obx(() {
if (provider.current.value == null || provider.channel.value == null) {
if (call.current.value == null || call.channel.value == null) {
return const SizedBox();
}
@ -18,11 +18,8 @@ class ChatCallCurrentIndicator extends StatelessWidget {
tileColor: Theme.of(context).colorScheme.surfaceContainerHigh,
contentPadding: const EdgeInsets.symmetric(horizontal: 32),
leading: const Icon(Icons.call),
title: Text(provider.channel.value!.name),
title: Text(call.channel.value!.name),
subtitle: Text('callAlreadyOngoing'.tr),
onTap: () {
provider.gotoScreen(context);
},
);
});
}

View File

@ -8,6 +8,7 @@ import 'package:solian/widgets/account/account_profile_popup.dart';
import 'package:solian/widgets/attachments/attachment_list.dart';
import 'package:solian/widgets/chat/chat_event_action_log.dart';
import 'package:solian/widgets/chat/chat_event_message.dart';
import 'package:solian/widgets/link_expansion.dart';
import 'package:timeago/timeago.dart' show format;
class ChatEvent extends StatelessWidget {
@ -37,10 +38,15 @@ class ChatEvent extends StatelessWidget {
return '$negativeSign${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds';
}
Widget _buildLinkExpansion() {
if (item.body['text'] == null) return const SizedBox();
return LinkExpansion(content: item.body['text']);
}
Widget _buildAttachment(BuildContext context, {bool isMinimal = false}) {
final attachments = item.body['attachments'] != null
? List<int>.from(item.body['attachments'].map((x) => x))
: List<int>.empty();
? List<String>.from(item.body['attachments']?.whereType<String>())
: List<String>.empty();
if (attachments.isEmpty) return const SizedBox();
@ -67,7 +73,7 @@ class ChatEvent extends StatelessWidget {
return Container(
key: Key('m${item.uuid}attachments-box'),
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(top: isMerged ? 0 : 4),
padding: EdgeInsets.only(top: isMerged ? 0 : 4, bottom: 4),
constraints: const BoxConstraints(
maxHeight: 720,
),
@ -75,7 +81,7 @@ class ChatEvent extends StatelessWidget {
key: Key('m${item.uuid}attachments'),
parentId: item.uuid,
attachmentsId: attachments,
viewport: 1,
isColumn: true,
),
);
}
@ -90,10 +96,13 @@ class ChatEvent extends StatelessWidget {
return const SizedBox();
}
return ChatEvent(
item: snapshot.data!.data,
isMerged: false,
isQuote: true,
return Container(
constraints: const BoxConstraints(maxWidth: 480),
child: ChatEvent(
item: snapshot.data!.data,
isMerged: false,
isQuote: true,
),
).paddingOnly(left: isMerged ? 52 : 0);
},
);
@ -188,8 +197,9 @@ class ChatEvent extends StatelessWidget {
),
],
).paddingOnly(right: 12),
_buildAttachment(context, isMinimal: isContentPreviewing)
.paddingOnly(left: isContentPreviewing ? 12 : 0),
_buildAttachment(context, isMinimal: isContentPreviewing).paddingOnly(
left: isContentPreviewing ? 12 : 56,
),
],
);
} else if (isQuote) {
@ -210,7 +220,9 @@ class ChatEvent extends StatelessWidget {
Row(
children: [
AccountAvatar(
content: item.sender.account.avatar, radius: 9),
content: item.sender.account.avatar,
radius: 9,
),
const SizedBox(width: 5),
Text(
item.sender.account.nick,
@ -221,8 +233,7 @@ class ChatEvent extends StatelessWidget {
],
),
_buildContent().paddingOnly(left: 0.5),
_buildAttachment(context, isMinimal: true)
.paddingOnly(left: 0),
_buildAttachment(context, isMinimal: true),
],
),
),
@ -231,6 +242,7 @@ class ChatEvent extends StatelessWidget {
).paddingOnly(left: isMerged ? 52 : 0, right: 4);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
key: Key('m${item.uuid}'),
children: [
Row(
@ -284,7 +296,8 @@ class ChatEvent extends StatelessWidget {
),
],
).paddingSymmetric(horizontal: 12),
_buildAttachment(context),
_buildLinkExpansion().paddingOnly(left: 52, right: 8),
_buildAttachment(context).paddingOnly(left: 56, right: 8),
],
);
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/channel.dart';
@ -83,12 +82,7 @@ class ChatEventList extends StatelessWidget {
),
);
},
).animate(key: Key('m-animation${item.uuid}')).slideY(
duration: 250.ms,
curve: Curves.fastEaseInToSlowEaseOut,
end: 0,
begin: 0.5,
);
);
},
);
}),

View File

@ -59,7 +59,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
final TextEditingController _textController = TextEditingController();
final FocusNode _focusNode = FocusNode();
final List<int> _attachments = List.empty(growable: true);
final List<String> _attachments = List.empty(growable: true);
Event? _editTo;
Event? _replyTo;
@ -68,7 +68,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentEditorPopup(
usage: 'm.attachment',
pool: 'messaging',
initialAttachments: _attachments,
onAdd: (value) {
setState(() {
@ -103,7 +103,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
final AttachmentUploaderController uploader = Get.find();
if (uploader.queueOfUpload.any(
((x) => x.usage == 'm.attachment' && x.isUploading),
((x) => x.isUploading),
)) {
context.showErrorDialog('attachmentUploadInProgress'.tr);
return;

View File

@ -0,0 +1,123 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:get/get.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/link_expander.dart';
import 'package:url_launcher/url_launcher_string.dart';
class LinkExpansion extends StatelessWidget {
final String content;
const LinkExpansion({super.key, required this.content});
Widget _buildImage(String url, {double? width, double? height}) {
return PlatformInfo.canCacheImage
? CachedNetworkImage(imageUrl: url, width: width, height: height)
: Image.network(url, width: width, height: height);
}
@override
Widget build(BuildContext context) {
final linkRegex = RegExp(
r'(?:(?:https?|ftp):\/\/|www\.)'
r'(?:[-_a-z0-9]+\.)*(?:[-a-z0-9]+\.[-a-z0-9]+)'
r'[^\s<]*'
r'[^\s<?!.,:*_~]',
);
final matches = linkRegex.allMatches(content);
if (matches.isEmpty) {
return const SizedBox();
}
final LinkExpandController expandController = Get.find();
return Wrap(
children: matches.map((x) {
return Container(
constraints: BoxConstraints(
maxWidth: matches.length == 1 ? 480 : 340,
),
child: FutureBuilder(
future: expandController.expandLink(x.group(0)!),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
final isRichDescription = [
"solsynth.dev",
].contains(Uri.parse(snapshot.data!.url).host);
return GestureDetector(
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if ([
(snapshot.data!.icon?.isNotEmpty ?? false),
snapshot.data!.siteName != null
].any((x) => x))
Row(
children: [
if (snapshot.data!.icon?.isNotEmpty ?? false)
ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
child: _buildImage(
snapshot.data!.icon!,
width: 32,
height: 32,
),
).paddingOnly(right: 8),
if (snapshot.data!.siteName != null)
Text(
snapshot.data!.siteName!,
style: Theme.of(context).textTheme.labelLarge,
),
],
).paddingOnly(
bottom: (snapshot.data!.icon?.isNotEmpty ?? false)
? 8
: 4,
),
if (snapshot.data!.image != null &&
(snapshot.data!.image?.startsWith('http') ?? false))
ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
child: _buildImage(
snapshot.data!.image!,
),
).paddingOnly(bottom: 8),
Text(
snapshot.data!.title ?? 'No Title',
maxLines: 1,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.bodyLarge,
),
if (snapshot.data!.description != null &&
isRichDescription)
MarkdownBody(data: snapshot.data!.description!)
else if (snapshot.data!.description != null)
Text(
snapshot.data!.description!,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
).paddingAll(12),
),
onTap: () {
launchUrlString(x.group(0)!);
},
);
},
),
);
}).toList(),
);
}
}

View File

@ -131,7 +131,7 @@ class MarkdownTextContent extends StatelessWidget {
child: AttachmentSelfContainedEntry(
isDense: true,
parentId: parentId,
id: int.parse(segments[1]),
rid: segments[1],
),
),
).paddingSymmetric(vertical: 4);

View File

@ -33,9 +33,11 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
final resp = await provider.getCurrentStatus();
final status = AccountStatus.fromJson(resp.body);
setState(() {
_accountStatus = status;
});
if (mounted) {
setState(() {
_accountStatus = status;
});
}
}
void _closeDrawer() {

View File

@ -19,7 +19,7 @@ class PostEditorCategoriesDialog extends StatelessWidget {
initialTags: controller.tags,
hintText: 'postTagsPlaceholder'.tr,
onUpdate: (value) {
controller.tags.value = value;
controller.tags.value = List.from(value, growable: true);
controller.tags.refresh();
},
),

View File

@ -14,12 +14,25 @@ class PostEditorOverviewDialog extends StatelessWidget {
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
autofocus: true,
autocorrect: true,
controller: controller.aliasController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
hintText: 'alias'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 16),
TextField(
autofocus: true,
autocorrect: true,
controller: controller.titleController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
isDense: true,
border: const OutlineInputBorder(),
hintText: 'title'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
@ -33,7 +46,8 @@ class PostEditorOverviewDialog extends StatelessWidget {
keyboardType: TextInputType.multiline,
controller: controller.descriptionController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
isDense: true,
border: const OutlineInputBorder(),
hintText: 'description'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),

View File

@ -20,7 +20,7 @@ class _PostEditorThumbnailDialogState extends State<PostEditorThumbnailDialog> {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentEditorPopup(
usage: 'i.attachment',
pool: 'interactive',
singleMode: true,
imageOnly: true,
autoUpload: true,
@ -84,8 +84,7 @@ class _PostEditorThumbnailDialogState extends State<PostEditorThumbnailDialog> {
actions: [
TextButton(
onPressed: () {
widget.controller.thumbnail.value =
int.tryParse(_attachmentController.text);
widget.controller.thumbnail.value = _attachmentController.text;
Navigator.pop(context);
},
child: Text('confirm'.tr),

View File

@ -1,5 +1,6 @@
import 'dart:math';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -40,24 +41,31 @@ class _PostActionState extends State<PostAction> {
}
Future<void> _doShare({bool noUri = false}) async {
ShareResult result;
String id;
final box = context.findRenderObject() as RenderBox?;
if (widget.item.alias?.isNotEmpty ?? false) {
id = '${widget.item.areaAlias}/${widget.item.alias}';
} else {
id = '${widget.item.id}';
}
if ((PlatformInfo.isAndroid || PlatformInfo.isIOS) && !noUri) {
await Share.shareUri(
Uri.parse('https://solsynth.dev/posts/${widget.item.id}'),
result = await Share.shareUri(
Uri.parse('https://solsynth.dev/posts/$id'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
} else {
final extraContent = [
final extraContent = <String?>[
widget.item.body['title'],
widget.item.body['description'],
];
].where((x) => x != null && x.isNotEmpty).toList();
final isExtraNotEmpty = extraContent.any((x) => x != null);
await Share.share(
result = await Share.share(
'postShareContent'.trParams({
'username': widget.item.author.nick,
'content':
'${extraContent.join('\n')}${isExtraNotEmpty ? '\n\n' : ''}${widget.item.body['content'] ?? 'no content'}',
'link': 'https://solsynth.dev/posts/${widget.item.id}',
'link': 'https://solsynth.dev/posts/$id',
}),
subject: 'postShareSubject'.trParams({
'username': widget.item.author.nick,
@ -65,6 +73,14 @@ class _PostActionState extends State<PostAction> {
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
}
if (result.status != ShareResultStatus.dismissed) {
await FirebaseAnalytics.instance.logShare(
contentType: 'Post',
itemId: widget.item.id.toString(),
method: result.raw,
);
}
}
@override
@ -86,9 +102,27 @@ class _PostActionState extends State<PostAction> {
'postActionList'.tr,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(
'#${widget.item.id.toString().padLeft(8, '0')}',
style: Theme.of(context).textTheme.bodySmall,
Row(
children: [
Text(
'#${widget.item.id.toString().padLeft(8, '0')}',
style: Theme.of(context).textTheme.bodySmall,
),
if (widget.item.alias?.isNotEmpty ?? false)
Text(
'·',
style: Theme.of(context).textTheme.bodySmall,
).paddingSymmetric(horizontal: 6),
if (widget.item.alias?.isNotEmpty ?? false)
Expanded(
child: Text(
'${widget.item.areaAlias}:${widget.item.alias}',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),

View File

@ -10,6 +10,7 @@ import 'package:solian/shells/title_shell.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_profile_popup.dart';
import 'package:solian/widgets/attachments/attachment_list.dart';
import 'package:solian/widgets/link_expansion.dart';
import 'package:solian/widgets/markdown_text_content.dart';
import 'package:solian/widgets/posts/post_tags.dart';
import 'package:solian/widgets/posts/post_quick_action.dart';
@ -78,23 +79,17 @@ class _PostItemState extends State<PostItem> {
Widget _buildThumbnail() {
if (widget.item.body['thumbnail'] == null) return const SizedBox();
const radius = BorderRadius.all(Radius.circular(8));
return AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
width: 0.3,
),
borderRadius: radius,
),
child: ClipRRect(
borderRadius: radius,
child: AttachmentSelfContainedEntry(
id: widget.item.body['thumbnail'],
parentId: 'p${item.id}-thumbnail',
),
final border = BorderSide(
color: Theme.of(context).dividerColor,
width: 0.3,
);
return Container(
decoration: BoxDecoration(border: Border(top: border, bottom: border)),
child: AspectRatio(
aspectRatio: 16 / 9,
child: AttachmentSelfContainedEntry(
rid: widget.item.body['thumbnail'],
parentId: 'p${item.id}-thumbnail',
),
),
);
@ -294,12 +289,41 @@ class _PostItemState extends State<PostItem> {
);
}
Widget _buildAttachments() {
final List<String> attachments = item.body['attachments'] is List
? List.from(item.body['attachments']?.whereType<String>())
: List.empty();
if (attachments.length > 3) {
return AttachmentList(
parentId: widget.item.id.toString(),
attachmentsId: attachments,
autoload: true,
isGrid: true,
).paddingOnly(left: 36, top: 4, bottom: 4);
} else if (attachments.length > 1) {
return AttachmentList(
parentId: widget.item.id.toString(),
attachmentsId: attachments,
autoload: true,
isColumn: true,
).paddingOnly(left: 60, right: 24);
} else {
return AttachmentList(
flatMaxHeight: MediaQuery.of(context).size.width,
parentId: widget.item.id.toString(),
attachmentsId: attachments,
autoload: true,
);
}
}
double _contentHeight = 0;
@override
Widget build(BuildContext context) {
final List<int> attachments = item.body['attachments'] is List
? item.body['attachments']?.cast<int>()
final List<String> attachments = item.body['attachments'] is List
? List.from(item.body['attachments']?.whereType<String>())
: List.empty();
final hasAttachment = attachments.isNotEmpty;
@ -307,7 +331,7 @@ class _PostItemState extends State<PostItem> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
_buildThumbnail().paddingOnly(bottom: 8),
_buildHeader().paddingSymmetric(horizontal: 12),
_buildHeaderDivider().paddingSymmetric(horizontal: 12),
Stack(
@ -355,6 +379,11 @@ class _PostItemState extends State<PostItem> {
),
],
),
LinkExpansion(content: item.body['content']).paddingOnly(
left: 8,
right: 8,
top: 4,
),
_buildFooter().paddingOnly(left: 16),
if (attachments.isNotEmpty)
Row(
@ -381,7 +410,7 @@ class _PostItemState extends State<PostItem> {
closedBuilder: (_, openContainer) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
_buildThumbnail().paddingOnly(bottom: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -447,26 +476,31 @@ class _PostItemState extends State<PostItem> {
],
),
if (widget.item.replyTo != null && widget.isShowEmbed)
_buildReply(context).paddingOnly(top: 4),
Container(
constraints: const BoxConstraints(maxWidth: 480),
padding: const EdgeInsets.only(top: 4),
child: _buildReply(context),
),
if (widget.item.repostTo != null && widget.isShowEmbed)
_buildRepost(context).paddingOnly(top: 4),
Container(
constraints: const BoxConstraints(maxWidth: 480),
padding: const EdgeInsets.only(top: 4),
child: _buildRepost(context),
),
_buildFooter().paddingOnly(left: 12),
LinkExpansion(content: item.body['content'])
.paddingOnly(top: 4),
],
),
),
],
).paddingOnly(
top: 10,
bottom: hasAttachment ? 10 : 0,
bottom: attachments.length == 1 ? 10 : 0,
right: 16,
left: 16,
),
AttachmentList(
flatMaxHeight: MediaQuery.of(context).size.width,
parentId: widget.item.id.toString(),
attachmentsId: attachments,
isGrid: attachments.length > 1,
),
_buildAttachments(),
if (widget.isShowReply || widget.isReactable)
PostQuickAction(
isShowReply: widget.isShowReply,
@ -479,8 +513,8 @@ class _PostItemState extends State<PostItem> {
});
},
).paddingOnly(
top: hasAttachment ? 10 : 6,
left: hasAttachment ? 24 : 60,
top: attachments.length == 1 ? 10 : 6,
left: attachments.length == 1 ? 24 : 60,
right: 16,
bottom: 10,
)

View File

@ -29,7 +29,7 @@ class _StickerUploadDialogState extends State<StickerUploadDialog> {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentEditorPopup(
usage: 'sticker',
pool: 'sticker',
singleMode: true,
imageOnly: true,
autoUpload: true,

View File

@ -14,7 +14,6 @@
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <media_kit_video/media_kit_video_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
@ -42,9 +41,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@ -11,7 +11,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
media_kit_libs_linux
media_kit_video
pasteboard
sentry_flutter
url_launcher_linux
)

View File

@ -9,7 +9,9 @@ import connectivity_plus
import desktop_drop
import device_info_plus
import file_selector_macos
import firebase_analytics
import firebase_core
import firebase_crashlytics
import firebase_messaging
import flutter_secure_storage_macos
import flutter_webrtc
@ -23,7 +25,6 @@ import pasteboard
import path_provider_foundation
import protocol_handler_macos
import screen_brightness_macos
import sentry_flutter
import share_plus
import shared_preferences_foundation
import sqflite
@ -35,7 +36,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
@ -49,7 +52,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ProtocolHandlerMacosPlugin.register(with: registry.registrar(forPlugin: "ProtocolHandlerMacosPlugin"))
ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))

View File

@ -8,25 +8,71 @@ PODS:
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- Firebase/Analytics (10.29.0):
- Firebase/Core
- Firebase/Core (10.29.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 10.29.0)
- Firebase/CoreOnly (10.29.0):
- FirebaseCore (= 10.29.0)
- Firebase/Crashlytics (10.29.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 10.29.0)
- Firebase/Messaging (10.29.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 10.29.0)
- firebase_analytics (11.2.1):
- Firebase/Analytics (= 10.29.0)
- firebase_core
- FlutterMacOS
- firebase_core (3.3.0):
- Firebase/CoreOnly (~> 10.29.0)
- FlutterMacOS
- firebase_crashlytics (4.0.4):
- Firebase/CoreOnly (~> 10.29.0)
- Firebase/Crashlytics (~> 10.29.0)
- firebase_core
- FlutterMacOS
- firebase_messaging (15.0.4):
- Firebase/CoreOnly (~> 10.29.0)
- Firebase/Messaging (~> 10.29.0)
- firebase_core
- FlutterMacOS
- FirebaseAnalytics (10.29.0):
- FirebaseAnalytics/AdIdSupport (= 10.29.0)
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseAnalytics/AdIdSupport (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleAppMeasurement (= 10.29.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseCore (10.29.0):
- FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.12)
- GoogleUtilities/Logger (~> 7.12)
- FirebaseCoreExtension (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseCoreInternal (10.29.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- FirebaseCrashlytics (10.29.0):
- FirebaseCore (~> 10.5)
- FirebaseInstallations (~> 10.0)
- FirebaseRemoteConfigInterop (~> 10.23)
- FirebaseSessions (~> 10.5)
- GoogleDataTransport (~> 9.2)
- GoogleUtilities/Environment (~> 7.8)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesObjC (~> 2.1)
- FirebaseInstallations (10.29.0):
- FirebaseCore (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
@ -41,6 +87,16 @@ PODS:
- GoogleUtilities/Reachability (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseRemoteConfigInterop (10.29.0)
- FirebaseSessions (10.29.0):
- FirebaseCore (~> 10.5)
- FirebaseCoreExtension (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleDataTransport (~> 9.2)
- GoogleUtilities/Environment (~> 7.13)
- GoogleUtilities/UserDefaults (~> 7.13)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesSwift (~> 2.1)
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
- flutter_webrtc (0.11.3):
@ -50,6 +106,26 @@ PODS:
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAppMeasurement (10.29.0):
- GoogleAppMeasurement/AdIdSupport (= 10.29.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (10.29.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 10.29.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (10.29.0):
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleDataTransport (9.4.1):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30911.0, >= 2.30908.0)
@ -65,6 +141,9 @@ PODS:
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/MethodSwizzler (7.13.3):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/Network (7.13.3):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
@ -79,17 +158,11 @@ PODS:
- GoogleUtilities/UserDefaults (7.13.3):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- livekit_client (2.2.3):
- livekit_client (2.2.4):
- FlutterMacOS
- WebRTC-SDK (= 125.6422.04)
- macos_window_utils (1.0.0):
- FlutterMacOS
- media_kit_libs_macos_video (1.0.4):
- FlutterMacOS
- media_kit_native_event_loop (1.0.0):
- FlutterMacOS
- media_kit_video (0.0.1):
- FlutterMacOS
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
@ -103,15 +176,10 @@ PODS:
- Flutter
- FlutterMacOS
- PromisesObjC (2.4.0)
- PromisesSwift (2.4.0):
- PromisesObjC (= 2.4.0)
- protocol_handler_macos (0.0.1):
- FlutterMacOS
- screen_brightness_macos (0.1.0):
- FlutterMacOS
- Sentry/HybridSDK (8.33.0)
- sentry_flutter (8.7.0):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.33.0)
- share_plus (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
@ -122,6 +190,9 @@ PODS:
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- FlutterMacOS
- WebRTC-SDK (125.6422.04)
@ -131,7 +202,9 @@ DEPENDENCIES:
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`)
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
- firebase_crashlytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos`)
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
@ -139,33 +212,35 @@ DEPENDENCIES:
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- protocol_handler_macos (from `Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos`)
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
- sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
SPEC REPOS:
trunk:
- Firebase
- FirebaseAnalytics
- FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal
- FirebaseCrashlytics
- FirebaseInstallations
- FirebaseMessaging
- FirebaseRemoteConfigInterop
- FirebaseSessions
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
- nanopb
- PromisesObjC
- Sentry
- PromisesSwift
- WebRTC-SDK
EXTERNAL SOURCES:
@ -177,8 +252,12 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
firebase_analytics:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos
firebase_core:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
firebase_crashlytics:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos
firebase_messaging:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
flutter_secure_storage_macos:
@ -193,12 +272,6 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
macos_window_utils:
:path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
media_kit_libs_macos_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
media_kit_native_event_loop:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos
media_kit_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
pasteboard:
@ -207,10 +280,6 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
protocol_handler_macos:
:path: Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos
screen_brightness_macos:
:path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos
sentry_flutter:
:path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos
share_plus:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
@ -219,6 +288,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
video_player_avfoundation:
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
wakelock_plus:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
@ -228,36 +299,40 @@ SPEC CHECKSUMS:
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2
Firebase: cec914dab6fd7b1bd8ab56ea07ce4e03dd251c2d
firebase_analytics: e1236d621a5d62d16a706315d198f3cd7f0e2fcb
firebase_core: 73185b844efc8a534e5744d68152e75e740922d2
firebase_crashlytics: a7b94c09eb4df16cabb0cfae4831ff4624a3d5d6
firebase_messaging: 167fdd90971720e0b62ccd6fa8d430b8af4ca6e9
FirebaseAnalytics: 23717de130b779aa506e757edb9713d24b6ffeda
FirebaseCore: 30e9c1cbe3d38f5f5e75f48bfcea87d7c358ec16
FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f
FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934
FirebaseCrashlytics: 34647b41e18de773717fdd348a22206f2f9bc774
FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd
FirebaseMessaging: 7b5d8033e183ab59eb5b852a53201559e976d366
FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d
FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
flutter_webrtc: 2b4e4a2de70a1485836e40fd71a7a94c77d49bd9
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
livekit_client: a59d8778582019242d96fe9da69d4ec48833b5ca
livekit_client: 95f3b71e6545845aa658a6df0a3a62dcc3471d7c
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
nanopb: 438bc412db1928dac798aa6fd75726007be04262
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_macos: d10a6c01d6373389ffd2278013ab4c47ed6d6daa
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6
sentry_flutter: e26b861f744e5037a3faf9bf56603ec65d658a61
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3

View File

@ -288,6 +288,7 @@
3399D490228B24CF009A79C7 /* ShellScript */,
C5DDC734703B72E778163C68 /* [CP] Embed Pods Frameworks */,
7009C53F1F4A3CB93BB64EF3 /* [CP] Copy Pods Resources */,
230664685E2D9269C5BF6747 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
);
buildRules = (
);
@ -400,6 +401,24 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
230664685E2D9269C5BF6747 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\"";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\n#!/bin/bash\nPATH=${PATH}:$FLUTTER_ROOT/bin:$HOME/.pub-cache/bin\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=$PODS_ROOT/FirebaseCrashlytics/upload-symbols --platform=macos --apple-project-path=${SRCROOT} --env-platform-name=${PLATFORM_NAME} --env-configuration=${CONFIGURATION} --env-project-dir=${PROJECT_DIR} --env-built-products-dir=${BUILT_PRODUCTS_DIR} --env-dwarf-dsym-folder-path=${DWARF_DSYM_FOLDER_PATH} --env-dwarf-dsym-file-name=${DWARF_DSYM_FILE_NAME} --env-infoplist-path=${INFOPLIST_PATH} --default-config=default\n";
};
291CAD95BC748648C4154147 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;

View File

@ -162,10 +162,10 @@ packages:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
version: "4.1.1"
cached_network_image_web:
dependency: transitive
description:
@ -274,10 +274,10 @@ packages:
dependency: "direct main"
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.5"
cupertino_icons:
dependency: "direct main"
description:
@ -346,18 +346,18 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
url: "https://pub.dev"
source: hosted
version: "5.5.0+1"
version: "5.6.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "2.0.0"
dismissible_page:
dependency: "direct main"
description:
@ -386,10 +386,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
field_suggestion:
dependency: "direct main"
description:
@ -446,6 +446,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.3+2"
firebase_analytics:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "064e5b57b0693305946b7caa6a80ed80a918f46804c247b6cd7ed9cd327df48f"
url: "https://pub.dev"
source: hosted
version: "11.2.1"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: d094547c9022c404b5ca39b7209607fc80e75e39d38875f050508fa4346b3e74
url: "https://pub.dev"
source: hosted
version: "4.2.1"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "06dc023b0144c0df630a56b6262cc9e7d6069fe78148853d97614dbefb6ea923"
url: "https://pub.dev"
source: hosted
version: "0.5.9+1"
firebase_core:
dependency: "direct main"
description:
@ -470,6 +494,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.17.4"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: "30260e1b8ad1464b41ca4531b44ce63d752daaf2f12c92ca6cdcd82b270abecc"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: a75e1826d92ea4e86e4a753c7b5d64b844a362676fa653185f1581c859186d18
url: "https://pub.dev"
source: hosted
version: "3.6.40"
firebase_messaging:
dependency: "direct main"
description:
@ -494,6 +534,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.8.12"
firebase_performance:
dependency: "direct main"
description:
name: firebase_performance
sha256: "6d17133458b9627f15f278d6f71bebbbce885d393f3462b690e55deeb5c36b90"
url: "https://pub.dev"
source: hosted
version: "0.10.0+4"
firebase_performance_platform_interface:
dependency: transitive
description:
name: firebase_performance_platform_interface
sha256: "28dc0a70a3459fe51d1c1be5754803a9a0db0e210322ec7526f6ce42bf6ad83e"
url: "https://pub.dev"
source: hosted
version: "0.1.4+40"
firebase_performance_web:
dependency: transitive
description:
name: firebase_performance_web
sha256: db91d86b34280f5253d2913945fdd51d7114486584a298a7bedf1c4b2ab08f79
url: "https://pub.dev"
source: hosted
version: "0.1.6+12"
fixnum:
dependency: transitive
description:
@ -559,10 +623,10 @@ packages:
dependency: "direct main"
description:
name: flutter_cache_manager
sha256: a77f77806a790eb9ba0118a5a3a936e81c4fea2b61533033b2b0c3d50bbde5ea
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
url: "https://pub.dev"
source: hosted
version: "3.4.0"
version: "3.4.1"
flutter_card_swiper:
dependency: "direct main"
description:
@ -849,18 +913,18 @@ packages:
dependency: "direct main"
description:
name: image_cropper
sha256: "7c3a85e54ec591738ee68f3cc9b8849bab0ec8e908bf8970645be959a079e17f"
sha256: fe37d9a129411486e0d93089b61bd326d05b89e78ad4981de54b560725bf5bd5
url: "https://pub.dev"
source: hosted
version: "8.0.1"
version: "8.0.2"
image_cropper_for_web:
dependency: transitive
description:
name: image_cropper_for_web
sha256: "65f3f23fb82ff153601f22864e07e2ec14a7859db9e6ee5f643f0cde5243e9f2"
sha256: "34256c8fb7fcb233251787c876bb37271744459b593a948a2db73caa323034d0"
url: "https://pub.dev"
source: hosted
version: "6.0.1"
version: "6.0.2"
image_cropper_platform_interface:
dependency: transitive
description:
@ -881,10 +945,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: c0e72ecd170b00a5590bb71238d57dc8ad22ee14c60c6b0d1a4e05cafbc5db4b
sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56"
url: "https://pub.dev"
source: hosted
version: "0.8.12+11"
version: "0.8.12+12"
image_picker_for_web:
dependency: transitive
description:
@ -1017,10 +1081,10 @@ packages:
dependency: "direct main"
description:
name: livekit_client
sha256: be2a3375851a6147d5de94a870edd6e831ab8d3d793e3563ba1ff1b05490b3de
sha256: fc86a8b65b74b41faef646cc671c1892a457c24fd69910e25f7a50dc8cdd3155
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.2.4"
logging:
dependency: transitive
description:
@ -1241,10 +1305,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
url: "https://pub.dev"
source: hosted
version: "2.2.9"
version: "2.2.10"
path_provider_foundation:
dependency: transitive
description:
@ -1289,10 +1353,10 @@ packages:
dependency: transitive
description:
name: permission_handler_android
sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
url: "https://pub.dev"
source: hosted
version: "12.0.8"
version: "12.0.12"
permission_handler_apple:
dependency: transitive
description:
@ -1305,10 +1369,10 @@ packages:
dependency: transitive
description:
name: permission_handler_html
sha256: "6cac773d389e045a8d4f85418d07ad58ef9e42a56e063629ce14c4c26344de24"
sha256: d220eb8476b466d58b161e10b3001d93999010a26228a3fb89c4280db1249546
url: "https://pub.dev"
source: hosted
version: "0.1.2"
version: "0.1.3+1"
permission_handler_platform_interface:
dependency: transitive
description:
@ -1557,22 +1621,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.2"
sentry:
dependency: transitive
description:
name: sentry
sha256: "0f787e27ff617e4f88f7074977240406a9c5509444bac64a4dfa5b3200fb5632"
url: "https://pub.dev"
source: hosted
version: "8.7.0"
sentry_flutter:
dependency: "direct main"
description:
name: sentry_flutter
sha256: fbbb47d72ccca48be25bf3c2ced6ab6e872991af3a0ba78e54be8d138f2e053f
url: "https://pub.dev"
source: hosted
version: "8.7.0"
share_plus:
dependency: "direct main"
description:
@ -1593,10 +1641,10 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
shared_preferences_android:
dependency: transitive
description:
@ -1609,10 +1657,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "776786cff96324851b656777648f36ac772d88bc4c669acff97b7fce5de3c849"
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
url: "https://pub.dev"
source: hosted
version: "2.5.1"
version: "2.5.2"
shared_preferences_linux:
dependency: transitive
description:
@ -1710,10 +1758,10 @@ packages:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
version: "2.5.4+2"
sqflite_common_ffi:
dependency: "direct dev"
description:
@ -1790,10 +1838,10 @@ packages:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
version: "3.2.0"
term_glyph:
dependency: transitive
description:
@ -1870,10 +1918,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9"
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
url: "https://pub.dev"
source: hosted
version: "6.3.8"
version: "6.3.9"
url_launcher_ios:
dependency: transitive
description:
@ -1950,18 +1998,18 @@ packages:
dependency: transitive
description:
name: volume_controller
sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9"
sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e
url: "https://pub.dev"
source: hosted
version: "2.0.7"
version: "2.0.8"
wakelock_plus:
dependency: "direct main"
description:
name: wakelock_plus
sha256: "4fa83a128b4127619e385f686b4f080a5d2de46cff8e8c94eccac5fcf76550e5"
sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484
url: "https://pub.dev"
source: hosted
version: "1.2.7"
version: "1.2.8"
wakelock_plus_platform_interface:
dependency: transitive
description:
@ -2014,10 +2062,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev"
source: hosted
version: "5.5.3"
version: "5.5.4"
win32_registry:
dependency: transitive
description:
@ -2051,5 +2099,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.5.0-259.0.dev <4.0.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.22.0"

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App"
publish_to: "none"
version: 1.2.1+13
version: 1.2.1+19
environment:
sdk: ">=3.3.4 <4.0.0"
@ -36,7 +36,6 @@ dependencies:
flutter_webrtc: ^0.11.4
wakelock_plus: ^1.2.5
cached_network_image: ^3.3.1
sentry_flutter: ^8.2.0
firebase_core: ^3.0.0
firebase_messaging: ^15.0.0
package_info_plus: ^8.0.0
@ -46,9 +45,6 @@ dependencies:
sqflite: ^2.3.3+1
protocol_handler: ^0.2.0
markdown: ^7.2.2
media_kit: ^1.1.10+1
media_kit_video: ^1.2.4
media_kit_libs_video: ^1.0.4
pasteboard: ^0.2.0
desktop_drop: ^0.4.4
badges: ^3.1.2
@ -69,6 +65,12 @@ dependencies:
field_suggestion: ^0.2.5
flutter_typeahead: ^5.2.0
collection: ^1.18.0
firebase_crashlytics: ^4.0.4
firebase_analytics: ^11.2.1
firebase_performance: ^0.10.0+4
media_kit: ^1.1.10+1
media_kit_video: ^1.2.4
media_kit_libs_video: ^1.0.4
dev_dependencies:
flutter_test:

View File

@ -21,7 +21,6 @@
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
@ -56,8 +55,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
ScreenBrightnessWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin"));
SentryFlutterPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(

View File

@ -18,7 +18,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
permission_handler_windows
protocol_handler_windows
screen_brightness_windows
sentry_flutter
share_plus
url_launcher_windows
)