Compare commits

...

34 Commits

Author SHA1 Message Date
18fba0c9e7 🚀 Launch 1.2.1+27 2024-09-05 21:41:01 +08:00
c68138e516 🐛 Fix missing gap between sections in dashboard 2024-09-05 21:13:50 +08:00
cedd0b083a 🐛 Fix overflow in dashboard 2024-09-05 20:28:22 +08:00
1a0721ba3a Better dashboard design for large screen (and mobile device) 2024-09-05 20:25:17 +08:00
a75f42e440 🚀 Launch 1.2.1+26 2024-09-03 23:22:18 +08:00
e4a6ff2da4 🌐 Localize dashboard 2024-09-03 23:21:46 +08:00
baa6b401d3 Better whats new 2024-09-03 23:07:20 +08:00
bd1369e72d ⬆️ Upgrade pod deps 2024-09-02 23:49:09 +08:00
10520b4448 🚀 Ready to launch 1.2.1+25 2024-09-02 23:16:54 +08:00
cab2217793 💄 Hint on dashboard 2024-09-02 23:15:24 +08:00
4e4e551e2f Daily sign 2024-09-02 23:11:40 +08:00
597a8a802a Dashboard basis 2024-09-01 17:20:26 +08:00
fff756cbe0 🐛 Bug fixes on link expansion doesn't show on merged chat event 2024-08-26 12:13:09 +08:00
e38778dbf9 🍱 Splash screen 2024-08-26 01:23:30 +08:00
cc9081b011 🐛 Fix image loading issue on web 2024-08-24 11:47:40 +08:00
14e8f7b775 🐛 Bug fixes and optimization 2024-08-23 23:16:41 +08:00
a70e6c7118 Typing indicator 2024-08-23 22:43:04 +08:00
48ca885a2c 🚀 Launch 1.2.1+22 2024-08-22 01:06:10 +08:00
09cb340a9d 🐛 Fix personalize page issue 2024-08-22 00:42:17 +08:00
b6ebd6bef6 🐛 Drawer will expand on mobile device 2024-08-21 20:55:22 +08:00
2ec25fd1a2 Drawer tooltip on collapse mode 2024-08-21 19:35:29 +08:00
bc99865ba8 💫 Animated collapsible sidebar 2024-08-21 19:11:27 +08:00
f834351ce2 Basis collapse sidebar 2024-08-21 17:00:59 +08:00
0f1a02f65b 🐛 Try to fix protocol handler issue on android 2024-08-21 16:02:00 +08:00
6ad0a34645 Call on large screen able to full screen 2024-08-21 15:57:45 +08:00
fdc71475fc 💄 Optimize message hint 2024-08-21 15:45:55 +08:00
047defebd1 🥅 Better request failed exceptions 2024-08-21 15:39:29 +08:00
6148e889aa 🥅 Better unauthorized exceptions 2024-08-21 15:25:50 +08:00
1d7affcd84 🐛 Bug fixes 2024-08-21 13:14:40 +08:00
cc1e0599aa 🐛 Fix link expand match markdown link 2024-08-21 10:06:05 +08:00
221b97901f 💄 Optimize uploader 2024-08-21 10:01:09 +08:00
498bb0e5fb Run upload chunks at the same time (max 3) 2024-08-21 09:33:34 +08:00
aa94dfcfe0 Multipart upload 2024-08-21 01:53:16 +08:00
65d9253876 🐛 Fix svg site icon cause invalid image data 2024-08-21 00:48:51 +08:00
88 changed files with 2743 additions and 1021 deletions

View File

@ -1,17 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -19,31 +19,31 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="Solian"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true">
android:label="Solian"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true">
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"/>
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
@ -58,29 +58,36 @@
<data android:host="sn.solsynth.dev" />
<data android:scheme="https" />
<data android:scheme="https" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="solink" />
</intent-filter>
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2"/>
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
@ -89,8 +96,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

7
build.yaml Normal file
View File

@ -0,0 +1,7 @@
targets:
$default:
builders:
json_serializable:
options:
explicit_to_json: true
field_rename: snake

View File

@ -38,125 +38,126 @@ PODS:
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Firebase/Analytics (10.29.0):
- Firebase/Analytics (11.0.0):
- Firebase/Core
- Firebase/Core (10.29.0):
- Firebase/Core (11.0.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 10.29.0)
- Firebase/CoreOnly (10.29.0):
- FirebaseCore (= 10.29.0)
- Firebase/Crashlytics (10.29.0):
- FirebaseAnalytics (~> 11.0.0)
- Firebase/CoreOnly (11.0.0):
- FirebaseCore (= 11.0.0)
- Firebase/Crashlytics (11.0.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 10.29.0)
- Firebase/Messaging (10.29.0):
- FirebaseCrashlytics (~> 11.0.0)
- Firebase/Messaging (11.0.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 10.29.0)
- Firebase/Performance (10.29.0):
- FirebaseMessaging (~> 11.0.0)
- Firebase/Performance (11.0.0):
- Firebase/CoreOnly
- FirebasePerformance (~> 10.29.0)
- firebase_analytics (11.2.1):
- Firebase/Analytics (= 10.29.0)
- FirebasePerformance (~> 11.0.0)
- firebase_analytics (11.3.0):
- Firebase/Analytics (= 11.0.0)
- firebase_core
- Flutter
- firebase_core (3.3.0):
- Firebase/CoreOnly (= 10.29.0)
- firebase_core (3.4.0):
- Firebase/CoreOnly (= 11.0.0)
- Flutter
- firebase_crashlytics (4.0.4):
- Firebase/Crashlytics (= 10.29.0)
- firebase_crashlytics (4.1.0):
- Firebase/Crashlytics (= 11.0.0)
- firebase_core
- Flutter
- firebase_messaging (15.0.4):
- Firebase/Messaging (= 10.29.0)
- firebase_messaging (15.1.0):
- Firebase/Messaging (= 11.0.0)
- firebase_core
- Flutter
- firebase_performance (0.10.0-4):
- Firebase/Performance (= 10.29.0)
- firebase_performance (0.10.0-5):
- Firebase/Performance (= 11.0.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)
- GoogleUtilities/UserDefaults (~> 7.8)
- PromisesObjC (~> 2.1)
- FirebaseMessaging (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleDataTransport (~> 9.3)
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
- GoogleUtilities/Environment (~> 7.8)
- 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)
- FirebaseABTesting (11.1.0):
- FirebaseCore (~> 11.0)
- FirebaseAnalytics (11.0.0):
- FirebaseAnalytics/AdIdSupport (= 11.0.0)
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.0.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseCore (11.0.0):
- FirebaseCoreInternal (~> 11.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreExtension (11.1.0):
- FirebaseCore (~> 11.0)
- FirebaseCoreInternal (11.1.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseCrashlytics (11.0.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfigInterop (~> 11.0)
- FirebaseSessions (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/Environment (~> 8.0)
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- FirebaseInstallations (11.1.0):
- FirebaseCore (~> 11.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.0.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- FirebasePerformance (11.0.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfig (~> 11.0)
- FirebaseSessions (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- FirebaseRemoteConfig (11.1.0):
- FirebaseABTesting (~> 11.0)
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfigInterop (~> 11.0)
- FirebaseSharedSwift (~> 11.0)
- GoogleUtilities/Environment (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseRemoteConfigInterop (11.1.0)
- FirebaseSessions (11.1.0):
- FirebaseCore (~> 11.0)
- FirebaseCoreExtension (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- PromisesSwift (~> 2.1)
- FirebaseSharedSwift (10.29.0)
- FirebaseSharedSwift (11.1.0)
- Flutter (1.0.0)
- flutter_keyboard_visibility (0.0.1):
- Flutter
- flutter_native_splash (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- flutter_webrtc (0.11.3):
@ -165,58 +166,54 @@ 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)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/AppDelegateSwizzler (7.13.3):
- GoogleAppMeasurement (11.0.0):
- GoogleAppMeasurement/AdIdSupport (= 11.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.0.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.0.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleUtilities/AppDelegateSwizzler (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (7.13.3):
- GoogleUtilities/Environment (8.0.2):
- GoogleUtilities/Privacy
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/ISASwizzler (7.13.3):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Logger (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/MethodSwizzler (7.13.3):
- GoogleUtilities/MethodSwizzler (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/Network (7.13.3):
- GoogleUtilities/Network (8.0.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.13.3)":
- "GoogleUtilities/NSData+zlib (8.0.2)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (7.13.3)
- GoogleUtilities/Reachability (7.13.3):
- GoogleUtilities/Privacy (8.0.2)
- GoogleUtilities/Reachability (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (7.13.3):
- GoogleUtilities/UserDefaults (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- image_cropper (0.0.4):
@ -233,11 +230,11 @@ PODS:
- Flutter
- media_kit_video (0.0.1):
- Flutter
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- package_info_plus (0.4.5):
- Flutter
- pasteboard (0.0.1):
@ -256,9 +253,9 @@ PODS:
- Flutter
- screen_brightness_ios (0.1.0):
- Flutter
- SDWebImage (5.19.6):
- SDWebImage/Core (= 5.19.6)
- SDWebImage/Core (5.19.6)
- SDWebImage (5.19.7):
- SDWebImage/Core (= 5.19.7)
- SDWebImage/Core (5.19.7)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@ -288,6 +285,7 @@ DEPENDENCIES:
- firebase_performance (from `.symlinks/plugins/firebase_performance/ios`)
- Flutter (from `Flutter`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
- gal (from `.symlinks/plugins/gal/darwin`)
@ -361,6 +359,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_keyboard_visibility:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_webrtc:
@ -412,40 +412,41 @@ SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
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
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9
firebase_analytics: 1a66fe8d4375eccff44671ea37897683a78b2675
firebase_core: ceec591a66629daaee82d3321551692c4a871493
firebase_crashlytics: e4f04180f443d5a8b56fbc0685bdbd7d90dd26f0
firebase_messaging: 15d8b557010f3bb7b98d0302e1c7c8fbcd244425
firebase_performance: d373c742649e2d85d92cc223b4511c3d132887ef
FirebaseABTesting: c2e22c3aab99afa81d0561708b2c1c356c556976
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
FirebaseCoreExtension: aa5c9779c2d0d39d83f1ceb3fdbafe80c4feecfa
FirebaseCoreInternal: adefedc9a88dbe393c4884640a73ec9e8e790f8c
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b
FirebaseInstallations: d0a8fea5a6fa91abc661591cf57c0f0d70863e57
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742
FirebasePerformance: efdc02bacb1b4710588c9f867011605c081cdf79
FirebaseRemoteConfig: 05521e937b72e01847a7128da5a492327364c705
FirebaseRemoteConfigInterop: abf8b1bbc0bf1b84abd22b66746926410bf91a87
FirebaseSessions: 78f137e68dc01ca71606169ba4ac73b98c13752a
FirebaseSharedSwift: 260a35e08943ec810d820a70bc0359136351d0c5
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
livekit_client: d079c5f040d4bf2b80440ff0ae997725a183e4bc
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
nanopb: 438bc412db1928dac798aa6fd75726007be04262
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
@ -455,7 +456,7 @@ SPEC CHECKSUMS:
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: a79252b60f4678812d94316c91da69ec83089c9f
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "darkbackground.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 233 KiB

View File

@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints>
</view>
</viewController>
@ -32,6 +38,7 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="1026" height="1024"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>

View File

@ -1,85 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>solink</string>
</array>
</dict>
</array>
<key>FirebaseMessagingAutoInitEnabled</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Solian</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Allow you take photo/video for your message or post</string>
<key>NSMicrophoneUsageDescription</key>
<string>Allow you record audio for your message or post</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow you add photo to your message or post</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>remote-notification</string>
<string>voip</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>NSUserActivityTypes</key>
<array>
<string>INSendMessageIntent</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>solink</string>
</array>
</dict>
</array>
<key>FirebaseMessagingAutoInitEnabled</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Solian</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>solian</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Allow you take photo/video for your message or post</string>
<key>NSMicrophoneUsageDescription</key>
<string>Allow you record audio for your message or post</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow you add photo to your message or post</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>remote-notification</string>
<string>voip</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>NSUserActivityTypes</key>
<array>
<string>INSendMessageIntent</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIStatusBarHidden</key>
<false/>
</dict>
</plist>

View File

@ -112,15 +112,19 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
label: 'bsPreparingData',
action: () async {
final AuthProvider auth = Get.find();
await Future.wait([
Get.find<StickerProvider>().refreshAvailableStickers(),
if (auth.isAuthorized.isTrue)
Get.find<ChannelProvider>().refreshAvailableChannel(),
if (auth.isAuthorized.isTrue)
Get.find<RelationshipProvider>().refreshRelativeList(),
if (auth.isAuthorized.isTrue)
Get.find<RealmProvider>().refreshAvailableRealms(),
]);
try {
await Future.wait([
Get.find<StickerProvider>().refreshAvailableStickers(),
if (auth.isAuthorized.isTrue)
Get.find<ChannelProvider>().refreshAvailableChannel(),
if (auth.isAuthorized.isTrue)
Get.find<RelationshipProvider>().refreshRelativeList(),
if (auth.isAuthorized.isTrue)
Get.find<RealmProvider>().refreshAvailableRealms(),
]);
} catch (e) {
context.showErrorDialog(e);
}
},
),
(

View File

@ -1,8 +1,11 @@
import 'dart:math';
import 'package:get/get.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/content/posts.dart';
import 'package:solian/providers/last_read.dart';
class PostListController extends GetxController {
String? author;
@ -14,7 +17,7 @@ class PostListController extends GetxController {
RxInt mode = 0.obs;
/// The paging controller for infinite loading.
/// Only available when mode is `0` or `1`.
/// Only available when mode is `0`, `1` or `2`.
PagingController<int, Post> pagingController =
PagingController(firstPageKey: 0);
@ -96,6 +99,9 @@ class PostListController extends GetxController {
final idx = <dynamic>{};
postList.retainWhere((x) => idx.add(x.id));
var lastId = postList.map((x) => x.id).reduce(max);
Get.find<LastReadProvider>().feedLastReadAt = lastId;
return result;
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
class RequestException implements Exception {
final Response data;
const RequestException(this.data);
@override
String toString() => 'Request failed ${data.statusCode}: ${data.bodyString}';
}

View File

@ -0,0 +1,6 @@
class UnauthorizedException implements Exception {
const UnauthorizedException();
@override
String toString() => 'Unauthorized';
}

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
extension SolianExtenions on BuildContext {
void showSnackbar(String content, {SnackBarAction? action}) {
@ -48,15 +50,48 @@ extension SolianExtenions on BuildContext {
}
Future<void> showErrorDialog(dynamic exception) {
var stack = StackTrace.current;
var stackTrace = '$stack';
Widget content = Text(exception.toString().capitalize!);
if (exception is UnauthorizedException) {
content = Text('errorHappenedUnauthorized'.tr);
}
if (exception is RequestException) {
String overall;
switch (exception.data.statusCode) {
case 400:
overall = 'errorHappenedRequestBad'.tr;
break;
case 401:
overall = 'errorHappenedUnauthorized'.tr;
break;
case 403:
overall = 'errorHappenedRequestForbidden'.tr;
break;
case 404:
overall = 'errorHappenedRequestNotFound'.tr;
break;
case null:
overall = 'errorHappenedRequestConnection'.tr;
break;
default:
overall = 'errorHappenedRequestUnknown'.tr;
break;
}
if (exception.data.statusCode != null) {
content = Text(
'$overall\n\n(${exception.data.statusCode}) ${exception.data.bodyString}',
);
} else {
content = Text(overall);
}
}
return showDialog<void>(
useRootNavigator: true,
context: this,
builder: (ctx) => AlertDialog(
title: Text('errorHappened'.tr),
content: Text('${exception.toString().capitalize!}\n\nStack Trace: $stackTrace'),
content: content,
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),

View File

@ -13,6 +13,8 @@ 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/daily_sign.dart';
import 'package:solian/providers/last_read.dart';
import 'package:solian/providers/link_expander.dart';
import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/theme_switcher.dart';
@ -129,6 +131,8 @@ class SolianApp extends StatelessWidget {
Get.lazyPut(() => RealmProvider());
Get.lazyPut(() => ChatCallProvider());
Get.lazyPut(() => AttachmentUploaderController());
Get.lazyPut(() => LinkExpandController());
Get.lazyPut(() => LinkExpandProvider());
Get.lazyPut(() => DailySignProvider());
Get.lazyPut(() => LastReadProvider());
}
}

View File

@ -1,5 +1,30 @@
import 'package:solian/models/account.dart';
class AttachmentPlaceholder {
int chunkCount;
int chunkSize;
Attachment meta;
AttachmentPlaceholder({
required this.chunkCount,
required this.chunkSize,
required this.meta,
});
factory AttachmentPlaceholder.fromJson(Map<String, dynamic> json) =>
AttachmentPlaceholder(
chunkCount: json['chunk_count'],
chunkSize: json['chunk_size'],
meta: Attachment.fromJson(json['meta']),
);
Map<String, dynamic> toJson() => {
'chunk_count': chunkCount,
'chunk_size': chunkSize,
'meta': meta.toJson(),
};
}
class Attachment {
int id;
DateTime createdAt;
@ -14,7 +39,9 @@ class Attachment {
String hash;
int destination;
bool isAnalyzed;
bool isUploaded;
Map<String, dynamic>? metadata;
Map<String, dynamic>? fileChunks;
bool isMature;
Account? account;
int? accountId;
@ -33,7 +60,9 @@ class Attachment {
required this.hash,
required this.destination,
required this.isAnalyzed,
required this.isUploaded,
required this.metadata,
required this.fileChunks,
required this.isMature,
required this.account,
required this.accountId,
@ -55,7 +84,9 @@ class Attachment {
hash: json['hash'],
destination: json['destination'],
isAnalyzed: json['is_analyzed'],
isUploaded: json['is_uploaded'],
metadata: json['metadata'],
fileChunks: json['file_chunks'],
isMature: json['is_mature'],
account:
json['account'] != null ? Account.fromJson(json['account']) : null,
@ -76,7 +107,9 @@ class Attachment {
'hash': hash,
'destination': destination,
'is_analyzed': isAnalyzed,
'is_uploaded': isUploaded,
'metadata': metadata,
'file_chunks': fileChunks,
'is_mature': isMature,
'account': account?.toJson(),
'account_id': accountId,

View File

@ -72,6 +72,15 @@ class Channel {
'realm_id': realmId,
'is_encrypted': isEncrypted,
};
@override
bool operator ==(Object other) {
if (other is! Channel) return false;
return id == other.id;
}
@override
int get hashCode => id;
}
class ChannelMember {

View File

@ -0,0 +1,49 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get/get.dart';
import 'package:solian/models/account.dart';
part 'daily_sign.g.dart';
@JsonSerializable()
class DailySignRecord {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
Account account;
int resultTier;
int resultExperience;
int accountId;
DailySignRecord({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.resultTier,
required this.resultExperience,
required this.account,
required this.accountId,
});
factory DailySignRecord.fromJson(Map<String, dynamic> json) =>
_$DailySignRecordFromJson(json);
Map<String, dynamic> toJson() => _$DailySignRecordToJson(this);
String get symbol => switch (resultTier) {
0 => '\n',
1 => '',
2 => '\n',
3 => '',
_ => '\n',
};
String get overviewSuggestion => switch (resultTier) {
0 => 'dailySignTier0'.tr,
1 => 'dailySignTier1'.tr,
2 => 'dailySignTier2'.tr,
3 => 'dailySignTier3'.tr,
_ => 'dailySignTier4'.tr,
};
}

View File

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'daily_sign.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DailySignRecord _$DailySignRecordFromJson(Map<String, dynamic> json) =>
DailySignRecord(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
resultTier: (json['result_tier'] as num).toInt(),
resultExperience: (json['result_experience'] as num).toInt(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$DailySignRecordToJson(DailySignRecord instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'account': instance.account.toJson(),
'result_tier': instance.resultTier,
'result_experience': instance.resultExperience,
'account_id': instance.accountId,
};

View File

@ -1,22 +1,26 @@
class NetworkPackage {
String method;
String? endpoint;
String? message;
Map<String, dynamic>? payload;
NetworkPackage({
required this.method,
this.endpoint,
this.message,
this.payload,
});
factory NetworkPackage.fromJson(Map<String, dynamic> json) => NetworkPackage(
method: json['w'],
endpoint: json['e'],
message: json['m'],
payload: json['p'],
);
Map<String, dynamic> toJson() => {
'w': method,
'e': endpoint,
'm': message,
'p': payload,
};

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/account_status.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/services.dart';
@ -33,15 +35,14 @@ class StatusProvider extends GetConnect {
Future<Response> getCurrentStatus() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth');
return await client.get('/users/me/status');
}
Future<Response> getSomeoneStatus(String name) =>
get('/users/$name/status');
Future<Response> getSomeoneStatus(String name) => get('/users/$name/status');
Future<Response> setStatus(
String type,
@ -53,7 +54,7 @@ class StatusProvider extends GetConnect {
DateTime? clearAt,
}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth');
@ -74,7 +75,7 @@ class StatusProvider extends GetConnect {
}
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -82,13 +83,13 @@ class StatusProvider extends GetConnect {
Future<Response> clearStatus() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth');
final resp = await client.delete('/users/me/status');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;

View File

@ -1,24 +1,27 @@
import 'dart:async';
import 'dart:io';
import 'dart:collection';
import 'dart:typed_data';
import 'package:cross_file/cross_file.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' show basename;
import 'package:solian/models/attachment.dart';
import 'package:solian/providers/content/attachment.dart';
class AttachmentUploadTask {
File file;
String usage;
XFile file;
String pool;
Map<String, dynamic>? metadata;
Map<String, int>? chunkFiles;
double progress = 0;
double? progress;
bool isUploading = false;
bool isCompleted = false;
dynamic error;
AttachmentUploadTask({
required this.file,
required this.usage,
required this.pool,
this.metadata,
});
}
@ -73,32 +76,36 @@ class AttachmentUploaderController extends GetxController {
_startProgressSyncTimer();
queueOfUpload[queueIndex].isUploading = true;
queueOfUpload[queueIndex].progress = 0;
final task = queueOfUpload[queueIndex];
final result = await _rawUploadAttachment(
await task.file.readAsBytes(),
task.file.path,
task.usage,
null,
onProgress: (value) {
queueOfUpload[queueIndex].progress = value;
_progressOfUpload = value;
},
onError: (err) {
queueOfUpload[queueIndex].error = err;
queueOfUpload[queueIndex].isUploading = false;
},
);
try {
final result = await _chunkedUploadAttachment(
task.file,
task.pool,
null,
onData: (_) {},
onProgress: (progress) {
queueOfUpload[queueIndex].progress = progress;
_progressOfUpload = progress;
},
);
return result;
} catch (err) {
queueOfUpload[queueIndex].error = err;
queueOfUpload[queueIndex].isUploading = false;
} finally {
_progressOfUpload = 1;
if (queueOfUpload[queueIndex].error == null) {
queueOfUpload.removeAt(queueIndex);
}
_stopProgressSyncTimer();
_syncProgress();
if (queueOfUpload[queueIndex].error == null) {
queueOfUpload.removeAt(queueIndex);
isUploading.value = false;
}
_stopProgressSyncTimer();
_syncProgress();
isUploading.value = false;
return result;
return null;
}
Future<void> performUploadQueue({
@ -115,24 +122,26 @@ class AttachmentUploaderController extends GetxController {
}
queueOfUpload[idx].isUploading = true;
queueOfUpload[idx].progress = 0;
final task = queueOfUpload[idx];
final result = await _rawUploadAttachment(
await task.file.readAsBytes(),
task.file.path,
task.usage,
null,
onProgress: (value) {
queueOfUpload[idx].progress = value;
_progressOfUpload = (idx + value) / queueOfUpload.length;
},
onError: (err) {
queueOfUpload[idx].error = err;
queueOfUpload[idx].isUploading = false;
},
);
_progressOfUpload = (idx + 1) / queueOfUpload.length;
if (result != null) onData(result);
try {
final result = await _chunkedUploadAttachment(
task.file,
task.pool,
null,
onData: (_) {},
onProgress: (progress) {
queueOfUpload[idx].progress = progress;
},
);
if (result != null) onData(result);
} catch (err) {
queueOfUpload[idx].error = err;
queueOfUpload[idx].isUploading = false;
} finally {
_progressOfUpload = (idx + 1) / queueOfUpload.length;
}
queueOfUpload[idx].isUploading = false;
queueOfUpload[idx].isCompleted = true;
@ -145,69 +154,94 @@ class AttachmentUploaderController extends GetxController {
isUploading.value = false;
}
Future<void> uploadAttachmentWithCallback(
Uint8List data,
String path,
String pool,
Map<String, dynamic>? metadata,
Function(Attachment?) callback,
) async {
if (isUploading.value) throw Exception('uploading blocked');
isUploading.value = true;
final result = await _rawUploadAttachment(
data,
path,
pool,
metadata,
onProgress: (progress) {
progressOfUpload.value = progress;
},
);
isUploading.value = false;
callback(result);
}
Future<Attachment?> uploadAttachment(
Future<Attachment?> uploadAttachmentFromData(
Uint8List data,
String path,
String pool,
Map<String, dynamic>? metadata,
) async {
if (isUploading.value) throw Exception('uploading blocked');
isUploading.value = true;
final result = await _rawUploadAttachment(
data,
path,
pool,
metadata,
onProgress: (progress) {
progressOfUpload.value = progress;
},
);
isUploading.value = false;
return result;
}
Future<Attachment?> _rawUploadAttachment(
Uint8List data, String path, String pool, Map<String, dynamic>? metadata,
{Function(double)? onProgress, Function(dynamic err)? onError}) async {
final AttachmentProvider provider = Get.find();
final AttachmentProvider attach = Get.find();
try {
final result = await provider.createAttachment(
final result = await attach.createAttachmentDirectly(
data,
path,
pool,
metadata,
onProgress: onProgress,
);
return result;
} catch (err) {
if (onError != null) {
onError(err);
}
} catch (_) {
return null;
} finally {
isUploading.value = false;
}
}
Future<Attachment?> _chunkedUploadAttachment(
XFile file,
String pool,
Map<String, dynamic>? metadata, {
required Function(AttachmentPlaceholder) onData,
required Function(double) onProgress,
}) async {
final AttachmentProvider attach = Get.find();
final holder = await attach.createAttachmentMultipartPlaceholder(
await file.length(),
file.path,
pool,
metadata,
);
onData(holder);
onProgress(0);
final filename = basename(file.path);
final chunks = holder.meta.fileChunks ?? {};
var currentTask = 0;
final queue = Queue<Future<void>>();
final activeTasks = <Future<void>>[];
for (final entry in chunks.entries) {
queue.add(() async {
final beginCursor = entry.value * holder.chunkSize;
final endCursor = (entry.value + 1) * holder.chunkSize;
final data = Uint8List.fromList(await file
.openRead(beginCursor, endCursor)
.expand((chunk) => chunk)
.toList());
final out = await attach.uploadAttachmentMultipartChunk(
data,
filename,
holder.meta.rid,
entry.key,
);
holder.meta = out;
currentTask++;
onProgress(currentTask / chunks.length);
onData(holder);
}());
}
while (queue.isNotEmpty || activeTasks.isNotEmpty) {
while (activeTasks.length < 3 && queue.isNotEmpty) {
final task = queue.removeFirst();
activeTasks.add(task);
task.then((_) => activeTasks.remove(task));
}
if (activeTasks.isNotEmpty) {
await Future.any(activeTasks);
}
}
return holder.meta;
}
}

View File

@ -7,6 +7,8 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart';
import 'package:get/get_connect/http/src/request/request.dart';
import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart';
@ -81,7 +83,7 @@ class AuthProvider extends GetConnect {
'grant_type': 'refresh_token',
});
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
credentials = TokenSet(
accessToken: resp.body['access_token'],
@ -128,7 +130,7 @@ class AuthProvider extends GetConnect {
}
Future<void> ensureCredentials() async {
if (isAuthorized.isFalse) throw Exception('unauthorized');
if (isAuthorized.isFalse) throw const UnauthorizedException();
if (credentials == null) await loadCredentials();
if (credentials!.isExpired) {
@ -158,7 +160,7 @@ class AuthProvider extends GetConnect {
'password': password,
});
if (resp.statusCode != 200) {
throw Exception(resp.body);
throw RequestException(resp);
} else if (resp.body['is_finished'] == false) {
throw RiskyAuthenticateException(resp.body['ticket']['id']);
}
@ -218,7 +220,7 @@ class AuthProvider extends GetConnect {
final client = configureClient('auth');
final resp = await client.get('/users/me');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
userProfile.value = resp.body;

View File

@ -2,6 +2,8 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:solian/models/call.dart';
@ -88,7 +90,7 @@ class ChatCallProvider extends GetxController {
Future<(String, String)> getRoomToken() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
@ -101,7 +103,7 @@ class ChatCallProvider extends GetxController {
endpoint = 'wss://${resp.body['endpoint']}';
return (token!, endpoint!);
} else {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
}

View File

@ -2,12 +2,13 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:path/path.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/services.dart';
import 'package:dio/dio.dart' as dio;
class AttachmentProvider extends GetConnect {
static Map<String, String> mimetypeOverrides = {
@ -83,16 +84,21 @@ class AttachmentProvider extends GetConnect {
return null;
}
Future<Attachment> createAttachment(
Uint8List data, String path, String pool, Map<String, dynamic>? metadata,
{Function(double)? onProgress}) async {
Future<Attachment> createAttachmentDirectly(
Uint8List data,
String path,
String pool,
Map<String, dynamic>? metadata,
) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
await auth.ensureCredentials();
final client = auth.configureClient(
'uc',
timeout: const Duration(minutes: 3),
);
final filePayload =
dio.MultipartFile.fromBytes(data, filename: basename(path));
final filePayload = MultipartFile(data, filename: basename(path));
final fileAlt = basename(path).contains('.')
? basename(path).substring(0, basename(path).lastIndexOf('.'))
: basename(path);
@ -105,30 +111,82 @@ class AttachmentProvider extends GetConnect {
if (mimetypeOverrides.keys.contains(fileExt)) {
mimetypeOverride = mimetypeOverrides[fileExt];
}
final payload = dio.FormData.fromMap({
final payload = FormData({
'alt': fileAlt,
'file': filePayload,
'pool': pool,
if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
'metadata': jsonEncode(metadata),
});
final resp = await dio.Dio(
dio.BaseOptions(
baseUrl: ServiceFinder.buildUrl('files', null),
headers: {'Authorization': 'Bearer ${auth.credentials!.accessToken}'},
),
).post(
'/attachments',
data: payload,
onSendProgress: (count, total) {
if (onProgress != null) onProgress(count / total);
},
);
final resp = await client.post('/attachments', payload);
if (resp.statusCode != 200) {
throw Exception(resp.data);
throw RequestException(resp);
}
return Attachment.fromJson(resp.data);
return Attachment.fromJson(resp.body);
}
Future<AttachmentPlaceholder> createAttachmentMultipartPlaceholder(
int size,
String path,
String pool,
Map<String, dynamic>? metadata,
) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('uc');
final fileAlt = basename(path).contains('.')
? basename(path).substring(0, basename(path).lastIndexOf('.'))
: basename(path);
final fileExt = basename(path)
.substring(basename(path).lastIndexOf('.') + 1)
.toLowerCase();
// Override for some files cannot be detected mimetype by server-side
String? mimetypeOverride;
if (mimetypeOverrides.keys.contains(fileExt)) {
mimetypeOverride = mimetypeOverrides[fileExt];
}
final resp = await client.post('/attachments/multipart', {
'alt': fileAlt,
'name': basename(path),
'size': size,
'pool': pool,
if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
'metadata': metadata,
});
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return AttachmentPlaceholder.fromJson(resp.body);
}
Future<Attachment> uploadAttachmentMultipartChunk(
Uint8List data,
String name,
String rid,
String cid,
) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient(
'uc',
timeout: const Duration(minutes: 3),
);
final payload = FormData({
'file': MultipartFile(data, filename: name),
});
final resp = await client.post('/attachments/multipart/$rid/$cid', payload);
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return Attachment.fromJson(resp.body);
}
Future<Response> updateAttachment(
@ -137,7 +195,7 @@ class AttachmentProvider extends GetConnect {
bool isMature = false,
}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('files');
@ -147,7 +205,7 @@ class AttachmentProvider extends GetConnect {
});
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -155,13 +213,13 @@ class AttachmentProvider extends GetConnect {
Future<Response> deleteAttachment(int id) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('files');
var resp = await client.delete('/attachments/$id');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/widgets/account/relative_select.dart';
@ -16,7 +18,7 @@ class ChannelProvider extends GetxController {
Future<void> refreshAvailableChannel() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
isLoading.value = true;
final resp = await listAvailableChannel();
@ -29,13 +31,13 @@ class ChannelProvider extends GetxController {
Future<Response> getChannel(String alias, {String realm = 'global'}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/$alias');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -44,13 +46,13 @@ class ChannelProvider extends GetxController {
Future<Response> getMyChannelProfile(String alias,
{String realm = 'global'}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/$alias/me');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -59,7 +61,7 @@ class ChannelProvider extends GetxController {
Future<Response?> getChannelOngoingCall(String alias,
{String realm = 'global'}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
@ -67,7 +69,7 @@ class ChannelProvider extends GetxController {
if (resp.statusCode == 404) {
return null;
} else if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -75,13 +77,13 @@ class ChannelProvider extends GetxController {
Future<Response> listChannel({String scope = 'global'}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
final resp = await client.get('/channels/$scope');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -89,13 +91,13 @@ class ChannelProvider extends GetxController {
Future<Response> listAvailableChannel({String realm = 'global'}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/me/available');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -103,13 +105,13 @@ class ChannelProvider extends GetxController {
Future<Response> createChannel(String scope, dynamic payload) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
final resp = await client.post('/channels/$scope', payload);
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -118,7 +120,7 @@ class ChannelProvider extends GetxController {
Future<Response?> createDirectChannel(
BuildContext context, String scope) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final related = await showModalBottomSheet(
useRootNavigator: true,
@ -141,7 +143,7 @@ class ChannelProvider extends GetxController {
'is_encrypted': false,
});
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -149,13 +151,13 @@ class ChannelProvider extends GetxController {
Future<Response> updateChannel(String scope, int id, dynamic payload) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging');
final resp = await client.put('/channels/$scope/$id', payload);
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;

View File

@ -1,4 +1,6 @@
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/services.dart';
@ -8,6 +10,22 @@ class PostProvider extends GetConnect {
httpClient.baseUrl = ServiceFinder.buildUrl('interactive', null);
}
Future<Response> seeWhatsNew(int pivot) async {
GetConnect client;
final AuthProvider auth = Get.find();
if (auth.isAuthorized.value) {
client = auth.configureClient('co');
} else {
client = ServiceFinder.configureClient('co');
}
final resp = await client.get('/whats-new?pivot=$pivot');
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return resp;
}
Future<Response> listRecommendations(int page,
{String? realm, String? channel}) async {
GetConnect client;
@ -28,7 +46,7 @@ class PostProvider extends GetConnect {
: '/recommendations/$channel?${queries.join('&')}',
);
if (resp.statusCode != 200) {
throw Exception(resp.body);
throw RequestException(resp);
}
return resp;
@ -36,7 +54,7 @@ class PostProvider extends GetConnect {
Future<Response> listDraft(int page) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final queries = [
'take=${10}',
@ -45,7 +63,7 @@ class PostProvider extends GetConnect {
final client = auth.configureClient('interactive');
final resp = await client.get('/posts/drafts?${queries.join('&')}');
if (resp.statusCode != 200) {
throw Exception(resp.body);
throw RequestException(resp);
}
return resp;
@ -63,7 +81,7 @@ class PostProvider extends GetConnect {
];
final resp = await get('/posts?${queries.join('&')}');
if (resp.statusCode != 200) {
throw Exception(resp.body);
throw RequestException(resp);
}
return resp;
@ -72,7 +90,7 @@ class PostProvider extends GetConnect {
Future<Response> listPostReplies(String alias, int page) async {
final resp = await get('/posts/$alias/replies?take=${10}&offset=$page');
if (resp.statusCode != 200) {
throw Exception(resp.body);
throw RequestException(resp);
}
return resp;
@ -81,7 +99,7 @@ class PostProvider extends GetConnect {
Future<Response> getPost(String alias) async {
final resp = await get('/posts/$alias');
if (resp.statusCode != 200) {
throw Exception(resp.body);
throw RequestException(resp);
}
return resp;
@ -90,7 +108,7 @@ class PostProvider extends GetConnect {
Future<Response> getArticle(String alias) async {
final resp = await get('/articles/$alias');
if (resp.statusCode != 200) {
throw Exception(resp.body);
throw RequestException(resp);
}
return resp;

View File

@ -1,4 +1,6 @@
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/realm.dart';
import 'package:solian/providers/auth.dart';
@ -8,7 +10,7 @@ class RealmProvider extends GetxController {
Future<void> refreshAvailableRealms() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
isLoading.value = true;
final resp = await listAvailableRealm();
@ -21,13 +23,13 @@ class RealmProvider extends GetxController {
Future<Response> getRealm(String alias) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth');
final resp = await client.get('/realms/$alias');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -35,13 +37,13 @@ class RealmProvider extends GetxController {
Future<Response> listAvailableRealm() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth');
final resp = await client.get('/realms/me/available');
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;

View File

@ -0,0 +1,37 @@
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/daily_sign.dart';
import 'package:solian/providers/auth.dart';
class DailySignProvider extends GetxController {
Future<DailySignRecord?> getToday() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('id');
final resp = await client.get('/daily/today');
if (resp.statusCode != 200 && resp.statusCode != 404) {
throw RequestException(resp);
} else if (resp.statusCode == 404) {
return null;
}
return DailySignRecord.fromJson(resp.body);
}
Future<DailySignRecord> signToday() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('id');
final resp = await client.post('/daily', {});
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return DailySignRecord.fromJson(resp.body);
}
}

View File

@ -0,0 +1,54 @@
import 'dart:math';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LastReadProvider extends GetxController {
int? _feedLastReadAt;
int? _messagesLastReadAt;
int? get feedLastReadAt => _feedLastReadAt;
int? get messagesLastReadAt => _messagesLastReadAt;
set feedLastReadAt(int? value) {
if (value == _feedLastReadAt) return;
final newValue = max(_feedLastReadAt ?? 0, value ?? 0);
if (newValue != _feedLastReadAt) {
_feedLastReadAt = newValue;
_saveToStorage();
}
}
set messagesLastReadAt(int? value) {
if (value == _messagesLastReadAt) return;
final newValue = max(_messagesLastReadAt ?? 0, value ?? 0);
if (newValue != _messagesLastReadAt) {
_messagesLastReadAt = newValue;
_saveToStorage();
}
}
LastReadProvider() {
_revertFromStorage();
}
Future<void> _revertFromStorage() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('feed_last_read_at')) {
_feedLastReadAt = prefs.getInt('feed_last_read_at')!;
}
if (prefs.containsKey('messages_last_read_at')) {
_messagesLastReadAt = prefs.getInt('messages_last_read_at');
}
}
Future<void> _saveToStorage() async {
final prefs = await SharedPreferences.getInstance();
if (_feedLastReadAt != null) {
prefs.setInt('feed_last_read_at', _feedLastReadAt!);
}
if (_messagesLastReadAt != null) {
prefs.setInt('messages_last_read_at', _messagesLastReadAt!);
}
}
}

View File

@ -5,10 +5,11 @@ import 'package:get/get.dart';
import 'package:solian/models/link.dart';
import 'package:solian/services.dart';
class LinkExpandController extends GetxController {
class LinkExpandProvider extends GetxController {
final Map<String, LinkMeta?> _cachedResponse = {};
Future<LinkMeta?> expandLink(String url) async {
log('[LinkExpander] Expanding link... $url');
final target = utf8.fuse(base64).encode(url);
if (_cachedResponse.containsKey(target)) return _cachedResponse[target];
final client = ServiceFinder.configureClient('dealer');

View File

@ -1,5 +1,6 @@
import 'package:floor/floor.dart';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/models/event.dart';
import 'package:solian/models/pagination.dart';
@ -16,6 +17,27 @@ Future<MessageHistoryDb> createHistoryDb() async {
.addMigrations([migration1to2]).build();
}
Future<(List<Event>, int)?> getWhatsNewEvents(int pivot, {take = 10}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return null;
final client = auth.configureClient('messaging');
final resp = await client.get(
'/whats-new?pivot=$pivot&take=$take',
);
if (resp.statusCode != 200) {
throw RequestException(resp);
}
final PaginationResult response = PaginationResult.fromJson(resp.body);
final result =
response.data?.map((e) => Event.fromJson(e)).toList() ?? List.empty();
return (result, response.count);
}
Future<Event?> getRemoteEvent(int id, Channel channel, String scope) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return null;
@ -29,20 +51,20 @@ Future<Event?> getRemoteEvent(int id, Channel channel, String scope) async {
if (resp.statusCode == 404) {
return null;
} else if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return Event.fromJson(resp.body);
}
Future<(List<Event>, int)?> getRemoteEvents(
Channel channel,
String scope, {
required int remainDepth,
bool Function(List<Event> items)? onBrake,
take = 10,
offset = 0,
}) async {
Channel channel,
String scope, {
required int remainDepth,
bool Function(List<Event> items)? onBrake,
take = 10,
offset = 0,
}) async {
if (remainDepth <= 0) {
return null;
}
@ -57,7 +79,7 @@ Future<(List<Event>, int)?> getRemoteEvents(
);
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
final PaginationResult response = PaginationResult.fromJson(resp.body);
@ -69,13 +91,13 @@ Future<(List<Event>, int)?> getRemoteEvents(
}
final expandResult = (await getRemoteEvents(
channel,
scope,
remainDepth: remainDepth - 1,
take: take,
offset: offset + result.length,
))
?.$1 ??
channel,
scope,
remainDepth: remainDepth - 1,
take: take,
offset: offset + result.length,
))
?.$1 ??
List.empty();
return ([...result, ...expandResult], response.count);

View File

@ -1,4 +1,5 @@
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/relations.dart';
import 'package:solian/providers/auth.dart';
@ -42,7 +43,7 @@ class RelationshipProvider extends GetxController {
final client = auth.configureClient('auth');
final resp = await client.post('/users/me/relations?related=$username', {});
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -57,7 +58,7 @@ class RelationshipProvider extends GetxController {
{},
);
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;
@ -71,7 +72,7 @@ class RelationshipProvider extends GetxController {
{'status': status},
);
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
throw RequestException(resp);
}
return resp;

View File

@ -6,6 +6,7 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/models/packet.dart';
import 'package:solian/models/pagination.dart';
@ -50,31 +51,31 @@ class WebSocketProvider extends GetxController {
}
final AuthProvider auth = Get.find();
await auth.ensureCredentials();
if (auth.credentials == null) await auth.loadCredentials();
final uri = Uri.parse(ServiceFinder.buildUrl(
'dealer',
'/api/ws?tk=${auth.credentials!.accessToken}',
).replaceFirst('http', 'ws'));
isConnecting.value = true;
try {
await auth.ensureCredentials();
final uri = Uri.parse(ServiceFinder.buildUrl(
'dealer',
'/api/ws?tk=${auth.credentials!.accessToken}',
).replaceFirst('http', 'ws'));
isConnecting.value = true;
websocket = WebSocketChannel.connect(uri);
await websocket?.ready;
} catch (e) {
listen();
isConnected.value = true;
} catch (err) {
log('Unable connect dealer via websocket... $err');
if (!noRetry) {
await auth.refreshCredentials();
return connect(noRetry: true);
}
} finally {
isConnecting.value = false;
}
listen();
isConnected.value = true;
isConnecting.value = false;
}
void disconnect() {
@ -87,6 +88,7 @@ class WebSocketProvider extends GetxController {
websocket?.stream.listen(
(event) {
final packet = NetworkPackage.fromJson(jsonDecode(event));
log('Websocket incoming message: ${packet.method} ${packet.message}');
stream.sink.add(packet);
},
onDone: () {
@ -147,8 +149,8 @@ class WebSocketProvider extends GetxController {
'device_token': token,
'device_id': deviceUuid,
});
if (resp.statusCode != 200) {
throw Exception(resp.bodyString);
if (resp.statusCode != 200 && resp.statusCode != 400) {
throw RequestException(resp);
}
}

View File

@ -12,6 +12,7 @@ import 'package:solian/screens/channel/channel_chat.dart';
import 'package:solian/screens/channel/channel_detail.dart';
import 'package:solian/screens/channel/channel_organize.dart';
import 'package:solian/screens/chat.dart';
import 'package:solian/screens/dashboard.dart';
import 'package:solian/screens/feed/search.dart';
import 'package:solian/screens/posts/post_detail.dart';
import 'package:solian/screens/feed/draft_box.dart';
@ -19,7 +20,7 @@ import 'package:solian/screens/realms.dart';
import 'package:solian/screens/realms/realm_detail.dart';
import 'package:solian/screens/realms/realm_organize.dart';
import 'package:solian/screens/realms/realm_view.dart';
import 'package:solian/screens/home.dart';
import 'package:solian/screens/feed.dart';
import 'package:solian/screens/posts/post_editor.dart';
import 'package:solian/screens/settings.dart';
import 'package:solian/shells/root_shell.dart';
@ -34,6 +35,14 @@ abstract class AppRouter {
child: child,
),
routes: [
GoRoute(
path: '/',
name: 'dashboard',
builder: (context, state) => TitleShell(
state: state,
child: const DashboardScreen(),
),
),
_feedRoute,
_chatRoute,
_realmRoute,
@ -63,9 +72,9 @@ abstract class AppRouter {
builder: (context, state, child) => child,
routes: [
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomeScreen(),
path: '/feed',
name: 'feed',
builder: (context, state) => const FeedScreen(),
),
GoRoute(
path: '/feed/search',

View File

@ -16,7 +16,7 @@ class NotificationScreen extends StatefulWidget {
class _NotificationScreenState extends State<NotificationScreen> {
bool _isBusy = false;
Future<void> markAllRead() async {
Future<void> _markAllRead() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return;
@ -40,7 +40,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
setState(() => _isBusy = false);
}
Future<void> markOneRead(notify.Notification element, int index) async {
Future<void> _markOneRead(notify.Notification element, int index) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return;
@ -64,7 +64,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
@override
Widget build(BuildContext context) {
final WebSocketProvider provider = Get.find();
final WebSocketProvider ws = Get.find();
return SizedBox(
height: MediaQuery.of(context).size.height * 0.85,
@ -83,7 +83,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
SliverToBoxAdapter(
child: const LinearProgressIndicator().animate().scaleX(),
),
if (provider.notifications.isEmpty)
if (ws.notifications.isEmpty)
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
@ -96,7 +96,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
),
),
),
if (provider.notifications.isNotEmpty)
if (ws.notifications.isNotEmpty)
SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
@ -104,14 +104,14 @@ class _NotificationScreenState extends State<NotificationScreen> {
child: ListTile(
leading: const Icon(Icons.checklist),
title: Text('notifyAllRead'.tr),
onTap: _isBusy ? null : () => markAllRead(),
onTap: _isBusy ? null : () => _markAllRead(),
),
),
),
SliverList.separated(
itemCount: provider.notifications.length,
itemCount: ws.notifications.length,
itemBuilder: (BuildContext context, int index) {
var element = provider.notifications[index];
var element = ws.notifications[index];
return Dismissible(
key: Key(const Uuid().v4()),
background: Container(
@ -135,7 +135,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
],
),
),
onDismissed: (_) => markOneRead(element, index),
onDismissed: (_) => _markOneRead(element, index),
);
},
separatorBuilder: (_, __) =>

View File

@ -30,8 +30,8 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
final _descriptionController = TextEditingController();
final _birthdayController = TextEditingController();
int? _avatar;
int? _banner;
String? _avatar;
String? _banner;
DateTime? _birthday;
bool _isBusy = false;
@ -109,11 +109,11 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
setState(() => _isBusy = true);
final AttachmentProvider provider = Get.find();
final AttachmentProvider attach = Get.find();
Attachment? attachResult;
try {
attachResult = await provider.createAttachment(
attachResult = await attach.createAttachmentDirectly(
await file.readAsBytes(),
file.path,
'avatar',
@ -129,7 +129,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
final resp = await client.put(
'/users/me/$position',
{'attachment': attachResult.id},
{'attachment': attachResult.rid},
);
if (resp.statusCode == 200) {
_syncWidget();

View File

@ -13,8 +13,13 @@ import 'package:livekit_client/livekit_client.dart' as livekit;
class CallScreen extends StatefulWidget {
final bool hideAppBar;
final bool isExpandable;
const CallScreen({super.key, this.hideAppBar = false});
const CallScreen({
super.key,
this.hideAppBar = false,
this.isExpandable = false,
});
@override
State<CallScreen> createState() => _CallScreenState();
@ -308,13 +313,24 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
),
);
}),
IconButton(
icon: _layoutMode == 0
? const Icon(Icons.view_list)
: const Icon(Icons.grid_view),
onPressed: () {
_switchLayout();
},
Row(
children: [
if (widget.isExpandable)
IconButton(
icon: const Icon(Icons.fullscreen),
onPressed: () {
ctrl.gotoScreen(context);
},
),
IconButton(
icon: _layoutMode == 0
? const Icon(Icons.view_list)
: const Icon(Icons.grid_view),
onPressed: () {
_switchLayout();
},
),
],
),
],
).paddingOnly(left: 20, right: 16),

View File

@ -24,6 +24,7 @@ import 'package:solian/widgets/channel/channel_call_indicator.dart';
import 'package:solian/widgets/chat/call/chat_call_action.dart';
import 'package:solian/widgets/chat/chat_event_list.dart';
import 'package:solian/widgets/chat/chat_message_input.dart';
import 'package:solian/widgets/chat/chat_typing_indicator.dart';
import 'package:solian/widgets/current_state_action.dart';
class ChannelChatScreen extends StatefulWidget {
@ -103,12 +104,18 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
setState(() => _isBusy = false);
}
final List<ChannelMember> _typingUsers = List.empty(growable: true);
final Map<int, Timer> _typingInactiveTimer = {};
void _listenMessages() {
final WebSocketProvider provider = Get.find();
_subscription = provider.stream.stream.listen((event) {
final WebSocketProvider ws = Get.find();
_subscription = ws.stream.stream.listen((event) {
switch (event.method) {
case 'events.new':
final payload = Event.fromJson(event.payload!);
final typingIdx =
_typingUsers.indexWhere((x) => x.id == payload.senderId);
if (typingIdx != -1) _typingUsers.removeAt(typingIdx);
_chatController.receiveEvent(payload);
break;
case 'calls.new':
@ -123,6 +130,25 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
setState(() => _ongoingCall = null);
}
break;
case 'status.typing':
if (event.payload?['channel_id'] != _channel!.id) break;
final member = ChannelMember.fromJson(event.payload!['member']);
if (member.id == _channelProfile!.id) break;
if (!_typingUsers.any((x) => x.id == member.id)) {
setState(() {
_typingUsers.add(member);
});
}
_typingInactiveTimer[member.id]?.cancel();
_typingInactiveTimer[member.id] = Timer(
const Duration(seconds: 3),
() {
setState(() {
_typingUsers.removeWhere((x) => x.id == member.id);
_typingInactiveTimer.remove(member.id);
});
},
);
}
});
}
@ -141,7 +167,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
switch (state) {
case AppLifecycleState.resumed:
if (_isOutOfSyncSince == null) break;
if (DateTime.now().difference(_isOutOfSyncSince!).inSeconds < 60) break;
if (DateTime.now().difference(_isOutOfSyncSince!).inSeconds < 30) break;
_keepUpdateWithServer();
break;
case AppLifecycleState.paused:
@ -269,26 +295,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
},
),
),
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();
@ -300,23 +306,28 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
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;
});
},
child: Column(
children: [
ChatTypingIndicator(users: _typingUsers),
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;
});
},
),
],
),
),
),
@ -331,7 +342,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
child: Row(children: [
VerticalDivider(width: 0.3, thickness: 0.3),
Expanded(
child: CallScreen(hideAppBar: true),
child: CallScreen(
hideAppBar: true,
isExpandable: true,
),
),
]),
);
@ -346,6 +360,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
@override
void dispose() {
for (var timer in _typingInactiveTimer.values) {
timer.cancel();
}
_subscription?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose();

View File

@ -80,13 +80,15 @@ class _ChatScreenState extends State<ChatScreen> {
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
),
onTap: () {
final ChannelProvider provider = Get.find();
provider
final ChannelProvider channels = Get.find();
channels
.createDirectChannel(context, 'global')
.then((resp) {
if (resp != null) {
_channels.refreshAvailableChannel();
}
}).catchError((e) {
context.showErrorDialog(e);
});
},
),
@ -125,6 +127,7 @@ class _ChatScreenState extends State<ChatScreen> {
noCategory: true,
channels: _channels.directChannels,
selfId: selfId,
useReplace: true,
),
),
),

497
lib/screens/dashboard.dart Normal file
View File

@ -0,0 +1,497 @@
import 'dart:developer';
import 'dart:math' hide log;
import 'package:collection/collection.dart';
import 'package:flutter/material.dart' hide Notification;
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/models/daily_sign.dart';
import 'package:solian/models/event.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/content/posts.dart';
import 'package:solian/providers/daily_sign.dart';
import 'package:solian/providers/last_read.dart';
import 'package:solian/providers/message/adaptor.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart';
import 'package:solian/widgets/chat/chat_event.dart';
import 'package:solian/widgets/posts/post_list.dart';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
late final AuthProvider _auth = Get.find();
late final LastReadProvider _lastRead = Get.find();
late final WebSocketProvider _ws = Get.find();
late final PostProvider _posts = Get.find();
late final DailySignProvider _dailySign = Get.find();
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
List<Notification> get _pendingNotifications =>
List<Notification>.from(_ws.notifications)
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
List<Post>? _currentPosts;
int? _currentPostsCount;
Future<void> _pullPosts() async {
print(_lastRead.feedLastReadAt);
if (_lastRead.feedLastReadAt == null) return;
log('[Dashboard] Pulling posts with pivot: ${_lastRead.feedLastReadAt}');
final resp = await _posts.seeWhatsNew(_lastRead.feedLastReadAt!);
final result = PaginationResult.fromJson(resp.body);
setState(() {
_currentPostsCount = result.count;
_currentPosts = result.data?.map((e) => Post.fromJson(e)).toList();
});
}
List<Event>? _currentMessages;
int? _currentMessagesCount;
Map<Channel, List<Event>>? get _currentGroupedMessages =>
_currentMessages?.groupListsBy((x) => x.channel!);
Future<void> _pullMessages() async {
if (_lastRead.messagesLastReadAt == null) return;
log('[Dashboard] Pulling messages with pivot: ${_lastRead.messagesLastReadAt}');
final out = await getWhatsNewEvents(_lastRead.messagesLastReadAt!);
if (out == null) return;
setState(() {
_currentMessages = out.$1;
_currentMessagesCount = out.$2;
});
}
bool _signingDaily = true;
DailySignRecord? _signRecord;
Future<void> _pullDaily() async {
try {
_signRecord = await _dailySign.getToday();
} catch (e) {
context.showErrorDialog(e);
}
setState(() => _signingDaily = false);
}
Future<void> _signDaily() async {
setState(() => _signingDaily = true);
try {
_signRecord = await _dailySign.signToday();
} catch (e) {
context.showErrorDialog(e);
}
setState(() => _signingDaily = false);
}
Future<void> _pullData() async {
if (!_auth.isAuthorized.value) return;
await Future.wait([
_pullPosts(),
_pullMessages(),
_pullDaily(),
]);
}
@override
void initState() {
super.initState();
_pullData();
}
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return RefreshIndicator(
onRefresh: _pullData,
child: ListView(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'today'.tr,
style: Theme.of(context).textTheme.headlineSmall,
),
Text(DateFormat('yyyy/MM/dd').format(DateTime.now())),
],
).paddingOnly(top: 8, left: 18, right: 18, bottom: 12),
Card(
child: ListTile(
leading: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('dd').format(DateTime.now()),
style: GoogleFonts.robotoMono(
fontSize: 22, height: 1.2),
),
Text(
DateFormat('yy/MM').format(DateTime.now()),
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
)
: Text(
_signRecord!.symbol,
style: GoogleFonts.notoSerifHk(fontSize: 20, height: 1),
).paddingSymmetric(horizontal: 9),
).paddingOnly(left: 4),
title: _signRecord == null
? Text('dailySign'.tr)
: Text(_signRecord!.overviewSuggestion),
subtitle: _signRecord == null
? Text('dailySignNone'.tr)
: Text('+${_signRecord!.resultExperience} EXP'),
trailing: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? IconButton(
tooltip: '上香求签',
icon: const Icon(Icons.local_fire_department),
onPressed: _signingDaily ? null : _signDaily,
)
: const SizedBox(),
),
),
).paddingSymmetric(horizontal: 8),
const Divider(thickness: 0.3).paddingSymmetric(vertical: 8),
/// Unread notifications
Obx(
() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'notification'.tr,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 18),
),
Text(
'notificationUnreadCount'.trParams({
'count': _ws.notifications.length.toString(),
}),
),
],
),
IconButton(
icon: const Icon(Icons.more_horiz),
onPressed: () {
showModalBottomSheet(
useRootNavigator: true,
isScrollControlled: true,
context: context,
builder: (context) => const NotificationScreen(),
).then((_) => _ws.notificationUnread.value = 0);
},
),
],
).paddingOnly(left: 18, right: 18, bottom: 8),
if (_ws.notifications.isNotEmpty)
SizedBox(
height: 76,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: min(_pendingNotifications.length, 10),
itemBuilder: (context, idx) {
final x = _pendingNotifications[idx];
return SizedBox(
width: min(360, width),
child: Card(
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 4,
),
title: Text(
x.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (x.subtitle != null)
Text(
x.subtitle!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
else
Text(
x.body,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
);
},
separatorBuilder: (_, __) => const Gap(4),
),
)
else
Card(
child: ListTile(
contentPadding:
const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Icons.inbox_outlined),
title: Text('notifyEmpty'.tr),
subtitle: Text('notifyEmptyCaption'.tr),
),
).paddingSymmetric(horizontal: 8),
],
).paddingOnly(bottom: 12),
),
/// Unread friends / followed people posts
if (_currentPosts?.isNotEmpty ?? false)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'feed'.tr,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 18),
),
Text(
'feedUnreadCount'.trParams({
'count': (_currentPostsCount ?? 0).toString(),
}),
),
],
),
IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
AppRouter.instance.goNamed('feed');
},
),
],
).paddingOnly(left: 18, right: 18, bottom: 8),
SizedBox(
height: 360,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _currentPosts!.length,
itemBuilder: (context, idx) {
final item = _currentPosts![idx];
return SizedBox(
width: min(480, width),
child: Card(
child: SingleChildScrollView(
child: PostListEntryWidget(
item: item,
isClickable: true,
isShowEmbed: true,
isNestedClickable: true,
onUpdate: (_) {
_pullPosts();
},
backgroundColor: Theme.of(context)
.colorScheme
.surfaceContainerLow,
),
),
).paddingSymmetric(horizontal: 8),
);
},
),
)
],
).paddingOnly(bottom: 12),
/// Unread messages part
if (_currentMessages?.isNotEmpty ?? false)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'messages'.tr,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 18),
),
Text(
'messagesUnreadCount'.trParams({
'count': (_currentMessagesCount ?? 0).toString(),
}),
),
],
),
IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
AppRouter.instance.goNamed('chat');
},
),
],
).paddingOnly(left: 18, right: 18, bottom: 8),
SizedBox(
height: 360,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _currentGroupedMessages!.length,
itemBuilder: (context, idx) {
final channel =
_currentGroupedMessages!.keys.elementAt(idx);
final itemList =
_currentGroupedMessages!.values.elementAt(idx);
return SizedBox(
width: min(520, width),
child: Card(
child: Column(
children: [
ListTile(
tileColor: Theme.of(context)
.colorScheme
.surfaceContainerHigh,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
)),
leading: CircleAvatar(
backgroundColor: channel.realmId == null
? Theme.of(context).colorScheme.primary
: Colors.transparent,
radius: 20,
child: FaIcon(
FontAwesomeIcons.hashtag,
color: channel.realmId == null
? Theme.of(context)
.colorScheme
.onPrimary
: Theme.of(context).colorScheme.primary,
size: 16,
),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
title: Text(channel.name),
subtitle: Text(channel.description),
onTap: () {
AppRouter.instance.pushNamed(
'channelChat',
pathParameters: {'alias': channel.alias},
queryParameters: {
if (channel.realmId != null)
'realm': channel.realm!.alias,
},
);
},
),
Expanded(
child: ListView.builder(
itemCount: itemList.length,
itemBuilder: (context, idx) {
final item = itemList[idx];
return ChatEvent(item: item).paddingOnly(
bottom: 8, top: 16, left: 8, right: 8);
},
),
),
],
),
).paddingSymmetric(horizontal: 8),
);
},
),
)
],
).paddingOnly(bottom: 12),
/// Footer
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Powered by Solar Network',
style: TextStyle(color: _unFocusColor, fontSize: 12),
),
Text(
'dashboardFooter'.tr,
style: [const Locale('zh', 'CN'), const Locale('zh', 'HK')]
.contains(Get.deviceLocale)
? GoogleFonts.notoSerifHk(
color: _unFocusColor,
fontSize: 12,
)
: TextStyle(
color: _unFocusColor,
fontSize: 12,
),
)
],
).paddingAll(8),
],
),
);
}
}

View File

@ -12,14 +12,14 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
import 'package:solian/widgets/posts/post_warped_list.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
class FeedScreen extends StatefulWidget {
const FeedScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
State<FeedScreen> createState() => _FeedScreenState();
}
class _HomeScreenState extends State<HomeScreen>
class _FeedScreenState extends State<FeedScreen>
with SingleTickerProviderStateMixin {
late final PostListController _postController;
late final TabController _tabController;
@ -65,7 +65,7 @@ class _HomeScreenState extends State<HomeScreen>
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
title: AppBarTitle('home'.tr),
title: AppBarTitle('feed'.tr),
centerTitle: false,
floating: true,
toolbarHeight: SolianTheme.toolbarHeight(context),

View File

@ -8,6 +8,8 @@ const i18nEnglish = {
'home': 'Home',
'guest': 'Guest',
'draft': 'Draft',
'dashboard': 'Dashboard',
'today': 'Today',
'draftSave': 'Save',
'draftBox': 'Draft Box',
'more': 'More',
@ -19,6 +21,17 @@ const i18nEnglish = {
'feedSearch': 'Search Feed',
'feedSearchWithTag': 'Searching with tag #@key',
'feedSearchWithCategory': 'Searching in category @category',
'feedUnreadCount': '@count posts you may missed',
'messages': 'Messages',
'messagesUnreadCount': '@count messages unread',
'dailySign': 'Daily Sign',
'dailySignNone': 'You haven\'t sign today',
'dailySignTier0': 'Everything may not be good',
'dailySignTier1': 'Something may be wrong',
'dailySignTier2': 'Just so so',
'dailySignTier3': 'Something may goes well',
'dailySignTier4': 'Everything will be awesome',
'dashboardFooter': 'Don\'t be serious, just for fun.',
'visitProfilePage': 'Visit Profile Page',
'profilePosts': 'Posts',
'profileAlbum': 'Album',
@ -40,7 +53,10 @@ const i18nEnglish = {
'openInAlbum': 'Open in album',
'openInBrowser': 'Open in browser',
'notification': 'Notification',
'notificationUnreadCount': '@count unread notifications',
'errorHappened': 'An error occurred',
'errorHappenedUnauthorized':
'Unauthorized request, please sign in or try resign in.',
'forgotPassword': 'Forgot password',
'email': 'Email',
'username': 'Username',
@ -174,7 +190,7 @@ const i18nEnglish = {
'attachmentAttached': 'Exists Files',
'attachmentUploadBlocked':
'Upload blocked, there is currently a task in progress...',
'attachmentAdd': 'Attach attachments',
'attachmentAdd': 'Attach file',
'attachmentAddGalleryPhoto': 'Gallery photo',
'attachmentAddGalleryVideo': 'Gallery video',
'attachmentAddCameraPhoto': 'Capture photo',
@ -381,4 +397,7 @@ const i18nEnglish = {
'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',
'collapse': 'Collapse',
'expand': 'Expand',
'typingMessage': '@user are typing...',
};

View File

@ -24,9 +24,22 @@ const i18nSimplifiedChinese = {
'alias': '别名',
'feed': '资讯',
'unlink': '移除链接',
'dashboard': '仪表盘',
'today': '今日',
'feedSearch': '搜索资讯',
'feedSearchWithTag': '检索带有 #@key 标签的资讯',
'feedSearchWithCategory': '检索位于分类 @category 的资讯',
'feedUnreadCount': '@count 条你可能错过的帖子',
'messages': '消息',
'messagesUnreadCount': '@count 条未读的消息',
'dailySign': '签到',
'dailySignNone': '今日未拜访佛祖',
'dailySignTier0': '诸事不宜',
'dailySignTier1': '有些不宜',
'dailySignTier2': '平平淡淡',
'dailySignTier3': '有些事宜',
'dailySignTier4': '诸事皆宜',
'dashboardFooter': '占卜多少沾点玩,人生还得靠实力',
'visitProfilePage': '造访个人主页',
'profilePosts': '帖子',
'profileAlbum': '相簿',
@ -40,7 +53,14 @@ const i18nSimplifiedChinese = {
'openInAlbum': '在相簿中打开',
'openInBrowser': '在浏览器中打开',
'notification': '通知',
'notificationUnreadCount': '@count 条未读通知',
'errorHappened': '发生错误了',
'errorHappenedUnauthorized': '未经授权的请求,请登录或尝试重新登录。',
'errorHappenedRequestBad': '请求错误,服务器拒绝处理该请求,请检查您的请求数据。',
'errorHappenedRequestForbidden': '请求错误,权限不足。',
'errorHappenedRequestNotFound': '请求错误,请求的数据不存在。',
'errorHappenedRequestConnection': '网络请求失败,请检查连接状态与服务状态后再试。',
'errorHappenedRequestUnknown': '请求错误,类型未知,请将本提示完整截图提交反馈。',
'forgotPassword': '忘记密码',
'email': '邮件地址',
'username': '用户名',
@ -347,4 +367,7 @@ const i18nSimplifiedChinese = {
'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。',
'messageHistoryWipe': '清除消息记录',
'unknown': '未知',
'collapse': '折叠',
'expand': '展开',
'typingMessage': '@user 正在输入中…',
};

View File

@ -28,11 +28,11 @@ class _AttachmentAttrEditorDialogState
bool _isMature = false;
Future<Attachment?> _updateAttachment() async {
final AttachmentProvider provider = Get.find();
final AttachmentProvider attach = Get.find();
setState(() => _isBusy = true);
try {
final resp = await provider.updateAttachment(
final resp = await attach.updateAttachment(
widget.item.id,
_altController.value.text,
isMature: _isMature,

View File

@ -64,7 +64,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return;
if (widget.singleMode) {
if (!widget.singleMode) {
final medias = await _imagePicker.pickMultiImage(
maxWidth: widget.imageMaxWidth,
maxHeight: widget.imageMaxHeight,
@ -72,8 +72,8 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
if (medias.isEmpty) return;
_enqueueTaskBatch(medias.map((x) {
final file = File(x.path);
return AttachmentUploadTask(file: file, usage: widget.pool);
final file = XFile(x.path);
return AttachmentUploadTask(file: file, pool: 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.pool),
AttachmentUploadTask(file: XFile(media.path), pool: widget.pool),
);
}
}
@ -95,9 +95,8 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
final media = await _imagePicker.pickVideo(source: ImageSource.gallery);
if (media == null) return;
final file = File(media.path);
_enqueueTask(
AttachmentUploadTask(file: file, usage: widget.pool),
AttachmentUploadTask(file: XFile(media.path), pool: widget.pool),
);
}
@ -113,7 +112,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.pool);
return AttachmentUploadTask(file: XFile(x.path), pool: widget.pool);
}));
}
@ -129,9 +128,8 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
}
if (media == null) return;
final file = File(media.path);
_enqueueTask(
AttachmentUploadTask(file: file, usage: widget.pool),
AttachmentUploadTask(file: XFile(media.path), pool: widget.pool),
);
}
@ -197,20 +195,16 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
if (_uploadController.isUploading.value) return;
_uploadController.uploadAttachmentWithCallback(
data,
'Pasted Image',
widget.pool,
null,
(item) {
if (item == null) return;
widget.onAdd(item.rid);
if (mounted) {
setState(() => _attachments.add(item));
if (widget.singleMode) Navigator.pop(context);
}
},
);
_uploadController
.uploadAttachmentFromData(data, 'Pasted Image', widget.pool, null)
.then((item) {
if (item == null) return;
widget.onAdd(item.rid);
if (mounted) {
setState(() => _attachments.add(item));
if (widget.singleMode) Navigator.pop(context);
}
});
}
String _formatBytes(int bytes, {int decimals = 2}) {
@ -304,7 +298,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
],
);
if (croppedFile == null) return;
_uploadController.queueOfUpload[queueIndex].file = File(croppedFile.path);
_uploadController.queueOfUpload[queueIndex].file = XFile(croppedFile.path);
_uploadController.queueOfUpload.refresh();
}
@ -347,9 +341,25 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
fontFamily: 'monospace',
),
),
Text(
'In queue #${index + 1}',
style: const TextStyle(fontSize: 12),
Row(
children: [
FutureBuilder(
future: element.file.length(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
return Text(
_formatBytes(snapshot.data!),
style: Theme.of(context).textTheme.bodySmall,
);
},
),
const SizedBox(width: 6),
if (element.progress != null)
Text(
'${(element.progress! * 100).toStringAsFixed(2)}%',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
),
@ -581,8 +591,8 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
onDragDone: (detail) async {
if (_uploadController.isUploading.value) return;
_enqueueTaskBatch(detail.files.map((x) {
final file = File(x.path);
return AttachmentUploadTask(file: file, usage: widget.pool);
final file = XFile(x.path);
return AttachmentUploadTask(file: file, pool: widget.pool);
}));
},
child: Column(
@ -596,15 +606,13 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
children: [
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
'attachmentAdd'.tr,
style:
Theme.of(context).textTheme.headlineSmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
'attachmentAdd'.tr,
style: Theme.of(context).textTheme.headlineSmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 10),
Obx(() {

View File

@ -147,17 +147,21 @@ class _AttachmentItemImage extends StatelessWidget {
errorWidget: (context, url, error) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Center(
child: const Icon(Icons.close, size: 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.close, size: 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
Text(error.toString()),
],
),
);
},
)
else
Image.network(
ServiceFinder.buildUrl('files', '/attachments/${item.id}'),
ServiceFinder.buildUrl('files', '/attachments/${item.rid}'),
fit: fit,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
@ -174,10 +178,14 @@ class _AttachmentItemImage extends StatelessWidget {
errorBuilder: (context, error, stackTrace) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Center(
child: const Icon(Icons.close, size: 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.close, size: 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
Text(error.toString()),
],
),
);
},

View File

@ -132,6 +132,9 @@ class _AttachmentListState extends State<AttachmentList> {
_getMetadataList();
}
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
@override
Widget build(BuildContext context) {
if (widget.attachmentsId.isEmpty) {
@ -139,12 +142,24 @@ class _AttachmentListState extends State<AttachmentList> {
}
if (_isLoading) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
child: const LinearProgressIndicator(),
);
return Row(
children: [
Icon(
Icons.file_copy,
size: 12,
color: _unFocusColor,
).paddingOnly(right: 5),
Text(
'attachmentHint'.trParams(
{'count': widget.attachmentsId.length.toString()},
),
style: TextStyle(color: _unFocusColor, fontSize: 12),
)
],
)
.paddingSymmetric(horizontal: 8)
.animate(onPlay: (c) => c.repeat(reverse: true))
.fadeIn(duration: 1250.ms);
}
if (widget.isColumn) {
@ -157,8 +172,11 @@ class _AttachmentListState extends State<AttachmentList> {
final element = _attachmentsMeta[idx];
idx++;
if (element == null) return const SizedBox();
double ratio = element.metadata!['ratio']?.toDouble() ?? 16 / 9;
double ratio = element.metadata?['ratio']?.toDouble() ?? 16 / 9;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
constraints: BoxConstraints(
maxWidth: widget.columnMaxWidth,
maxHeight: 640,
@ -204,6 +222,7 @@ class _AttachmentListState extends State<AttachmentList> {
final element = _attachmentsMeta[idx];
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,

View File

@ -197,8 +197,11 @@ class ChatEvent extends StatelessWidget {
),
],
).paddingOnly(right: 12),
if (!isContentPreviewing)
_buildLinkExpansion().paddingOnly(left: 52, right: 8),
_buildAttachment(context, isMinimal: isContentPreviewing).paddingOnly(
left: isContentPreviewing ? 12 : 56,
right: 8,
),
],
);

View File

@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/models/event.dart';
import 'package:solian/providers/last_read.dart';
import 'package:solian/widgets/chat/chat_event.dart';
import 'package:solian/widgets/chat/chat_event_action.dart';
@ -39,6 +40,9 @@ class ChatEventList extends StatelessWidget {
key: Key('chat-history#${channel.id}'),
itemCount: chatController.currentEvents.length,
itemBuilder: (context, index) {
Get.find<LastReadProvider>().messagesLastReadAt =
chatController.currentEvents[index].id;
bool isMerged = false, hasMerged = false;
if (index > 0) {
hasMerged = _checkMessageMergeable(

View File

@ -1,3 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
@ -7,10 +10,12 @@ import 'package:solian/exts.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/channel.dart';
import 'package:solian/models/event.dart';
import 'package:solian/models/packet.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/attachment_uploader.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/attachments/attachment_editor.dart';
import 'package:solian/widgets/chat/chat_event.dart';
@ -196,6 +201,36 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
}
}
Timer? _typingNotifyTimer;
bool _typingStatus = false;
Future<void> _sendTypingStatus() async {
final WebSocketProvider ws = Get.find();
ws.websocket?.sink.add(jsonEncode(
NetworkPackage(
method: 'status.typing',
endpoint: 'messaging',
payload: {
'channel_id': widget.channel.id,
},
).toJson(),
));
}
void _pingEnterMessageStatus() {
if (!_typingStatus) {
_sendTypingStatus();
_typingStatus = true;
}
if (_typingNotifyTimer == null || !_typingNotifyTimer!.isActive) {
_typingNotifyTimer?.cancel();
_typingNotifyTimer = Timer(const Duration(milliseconds: 1850), () {
_typingStatus = false;
});
}
}
void _resetInput() {
if (widget.onReset != null) widget.onReset!();
_editTo = null;
@ -239,7 +274,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
var insertText = '';
if (suggestion.type == 'emotes') {
insertText = suggestion.content;
insertText = '${suggestion.content} ';
startText = replaceText.replaceFirstMapped(
RegExp(r':(?:([-\w]+)~)?([-\w]+)$'),
(Match m) => insertText,
@ -247,7 +282,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
}
if (suggestion.type == 'users') {
insertText = suggestion.content;
insertText = '${suggestion.content} ';
startText = replaceText.replaceFirstMapped(
RegExp(r'(?:\s|^)@([-\w]+)$'),
(Match m) => insertText,
@ -269,6 +304,20 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
super.initState();
_textController.addListener(_pingEnterMessageStatus);
}
@override
void dispose() {
_textController.removeListener(_pingEnterMessageStatus);
_textController.dispose();
_typingNotifyTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final notifyBannerActions = [

View File

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/models/channel.dart';
class ChatTypingIndicator extends StatefulWidget {
final List<ChannelMember> users;
const ChatTypingIndicator({super.key, required this.users});
@override
State<ChatTypingIndicator> createState() => _ChatTypingIndicatorState();
}
class _ChatTypingIndicatorState extends State<ChatTypingIndicator>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 250),
vsync: this,
);
late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(covariant ChatTypingIndicator oldWidget) {
if (widget.users.isNotEmpty) {
_controller.animateTo(1);
} else {
_controller.animateTo(0);
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor: _animation,
axis: Axis.vertical,
axisAlignment: -1,
child: Row(
children: [
const Icon(Icons.more_horiz),
const SizedBox(width: 6),
Text('typingMessage'.trParams({
'user': widget.users.map((x) => x.account.nick).join(', '),
})),
],
).paddingSymmetric(horizontal: 16),
);
}
}

View File

@ -1,6 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/link_expander.dart';
@ -12,25 +14,53 @@ class LinkExpansion extends StatelessWidget {
const LinkExpansion({super.key, required this.content});
Widget _buildImage(String url, {double? width, double? height}) {
if (url.endsWith('svg')) {
return SvgPicture.network(url, width: width, height: height);
}
return PlatformInfo.canCacheImage
? CachedNetworkImage(imageUrl: url, width: width, height: height)
: Image.network(url, width: width, height: height);
? CachedNetworkImage(
imageUrl: url,
width: width,
height: height,
errorWidget: (context, url, error) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Center(
child: const Icon(Icons.close, size: 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
),
);
},
)
: Image.network(
url,
width: width,
height: height,
errorBuilder: (context, error, stackTrace) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: Center(
child: const Icon(Icons.close, size: 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
),
);
},
);
}
@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<?!.,:*_~]',
r'(?<!\()(?:(?:https?):\/\/|www\.)(?:[-_a-z0-9]+\.)*(?:[-a-z0-9]+\.[-a-z0-9]+)[^\s<]*[^\s<?!.,:*_~]',
);
final matches = linkRegex.allMatches(content);
if (matches.isEmpty) {
return const SizedBox();
}
final LinkExpandController expandController = Get.find();
final LinkExpandProvider expandController = Get.find();
return Wrap(
children: matches.map((x) {
@ -46,7 +76,7 @@ class LinkExpansion extends StatelessWidget {
}
final isRichDescription = [
"solsynth.dev",
'solsynth.dev',
].contains(Uri.parse(snapshot.data!.url).host);
return GestureDetector(

View File

@ -4,9 +4,14 @@ import 'package:get/utils.dart';
abstract class AppNavigation {
static List<AppNavigationDestination> destinations = [
AppNavigationDestination(
icon: Icons.home,
label: 'home'.tr,
page: 'home',
icon: Icons.dashboard,
label: 'dashboard'.tr,
page: 'dashboard',
),
AppNavigationDestination(
icon: Icons.newspaper,
label: 'feed'.tr,
page: 'feed',
),
AppNavigationDestination(
icon: Icons.workspaces,

View File

@ -13,7 +13,7 @@ import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_status_action.dart';
import 'package:solian/widgets/navigation/app_navigation.dart';
import 'package:badges/badges.dart' as badges;
import 'package:solian/widgets/navigation/app_navigation_regions.dart';
import 'package:solian/widgets/navigation/app_navigation_region.dart';
class AppNavigationDrawer extends StatefulWidget {
final String? routeName;
@ -24,7 +24,23 @@ class AppNavigationDrawer extends StatefulWidget {
State<AppNavigationDrawer> createState() => _AppNavigationDrawerState();
}
class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
class _AppNavigationDrawerState extends State<AppNavigationDrawer>
with TickerProviderStateMixin {
bool _isCollapsed = true;
late final AnimationController _drawerAnimationController =
AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
late final Animation<double> _drawerAnimation = Tween<double>(
begin: 80.0,
end: 304.0,
).animate(CurvedAnimation(
parent: _drawerAnimationController,
curve: Curves.easeInOut,
));
AccountStatus? _accountStatus;
Future<void> _getStatus() async {
@ -40,137 +56,225 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
}
}
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
Widget _buildUserInfo() {
return Obx(() {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
if (_isCollapsed) {
return InkWell(
child: const Icon(Icons.account_circle).paddingSymmetric(
horizontal: 28,
vertical: 20,
),
onTap: () {
AppRouter.instance.goNamed('account');
_closeDrawer();
},
);
}
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 28),
leading: const Icon(Icons.account_circle),
title: !_isCollapsed ? Text('guest'.tr) : null,
subtitle: !_isCollapsed ? Text('unsignedIn'.tr) : null,
onTap: () {
AppRouter.instance.goNamed('account');
_closeDrawer();
},
);
}
final leading = Obx(() {
final statusBadgeColor = _accountStatus != null
? StatusProvider.determineStatus(_accountStatus!).$2
: Colors.grey;
final RelationshipProvider relations = Get.find();
final accountNotifications = relations.friendRequestCount.value;
return badges.Badge(
badgeContent: Text(
accountNotifications.toString(),
style: const TextStyle(color: Colors.white),
),
showBadge: accountNotifications > 0,
position: badges.BadgePosition.topEnd(
top: -10,
end: -6,
),
child: badges.Badge(
showBadge: _accountStatus != null,
badgeStyle: badges.BadgeStyle(badgeColor: statusBadgeColor),
position: badges.BadgePosition.bottomEnd(
bottom: 0,
end: -2,
),
child: AccountAvatar(
content: auth.userProfile.value!['avatar'],
),
),
);
});
return InkWell(
child: !_isCollapsed
? Row(
children: [
leading,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
auth.userProfile.value!['nick'],
maxLines: 1,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.bodyLarge,
).paddingOnly(left: 16),
Builder(
builder: (context) {
if (_accountStatus == null) {
return Text('loading'.tr).paddingOnly(left: 16);
}
final info = StatusProvider.determineStatus(
_accountStatus!,
);
return Text(
info.$3,
maxLines: 1,
overflow: TextOverflow.fade,
style: TextStyle(
color: _unFocusColor,
),
).paddingOnly(left: 16);
},
),
],
),
),
],
).paddingSymmetric(horizontal: 20, vertical: 16)
: leading.paddingSymmetric(horizontal: 20, vertical: 16),
onTap: () {
AppRouter.instance.goNamed('account');
_closeDrawer();
},
onLongPress: () {
showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (context) => AccountStatusAction(
currentStatus: _accountStatus!.status,
),
).then((val) {
if (val == true) _getStatus();
});
},
);
});
}
void _expandDrawer() {
_drawerAnimationController.animateTo(1);
}
void _collapseDrawer() {
_drawerAnimationController.animateTo(0);
}
void _closeDrawer() {
_autoResize();
rootScaffoldKey.currentState!.closeDrawer();
}
void _autoResize() {
if (SolianTheme.isExtraLargeScreen(context)) {
_expandDrawer();
} else if (SolianTheme.isLargeScreen(context)) {
_collapseDrawer();
} else {
_drawerAnimationController.value = 1;
}
}
@override
void initState() {
super.initState();
_getStatus();
Future.delayed(Duration.zero, () => _autoResize());
_drawerAnimationController.addListener(() {
if (_drawerAnimation.value > 180 && _isCollapsed) {
setState(() => _isCollapsed = false);
} else if (_drawerAnimation.value < 180 && !_isCollapsed) {
setState(() => _isCollapsed = true);
}
});
}
@override
void dispose() {
_drawerAnimationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
return Drawer(
backgroundColor:
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
return AnimatedBuilder(
animation: _drawerAnimation,
builder: (context, child) {
return Drawer(
width: _drawerAnimation.value,
backgroundColor:
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
child: child,
);
},
child: SafeArea(
bottom: false,
child: Column(
children: [
Obx(() {
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 28),
leading: const Icon(Icons.account_circle),
title: Text('guest'.tr),
subtitle: Text('unsignedIn'.tr),
onTap: () {
AppRouter.instance.goNamed('account');
_closeDrawer();
},
);
}
return ListTile(
contentPadding: const EdgeInsets.only(left: 20, right: 20),
title: Text(
auth.userProfile.value!['nick'],
maxLines: 1,
overflow: TextOverflow.fade,
),
subtitle: Builder(
builder: (context) {
if (_accountStatus == null) {
return Text('loading'.tr);
}
final info = StatusProvider.determineStatus(
_accountStatus!,
);
return Text(
info.$3,
maxLines: 1,
overflow: TextOverflow.fade,
);
},
),
leading: Obx(() {
final statusBadgeColor = _accountStatus != null
? StatusProvider.determineStatus(
_accountStatus!,
).$2
: Colors.grey;
final RelationshipProvider relations = Get.find();
final accountNotifications =
relations.friendRequestCount.value;
return badges.Badge(
badgeContent: Text(
accountNotifications.toString(),
style: const TextStyle(color: Colors.white),
),
showBadge: accountNotifications > 0,
position: badges.BadgePosition.topEnd(
top: -10,
end: -6,
),
child: badges.Badge(
showBadge: _accountStatus != null,
badgeStyle:
badges.BadgeStyle(badgeColor: statusBadgeColor),
position: badges.BadgePosition.bottomEnd(
bottom: 0,
end: -2,
),
child: AccountAvatar(
content: auth.userProfile.value!['avatar'],
),
),
);
}),
onTap: () {
AppRouter.instance.goNamed('account');
_closeDrawer();
},
onLongPress: () {
showModalBottomSheet(
useRootNavigator: true,
context: context,
builder: (context) => AccountStatusAction(
currentStatus: _accountStatus!.status,
),
).then((val) {
if (val == true) _getStatus();
});
},
);
}).paddingSymmetric(vertical: 8),
_buildUserInfo().paddingSymmetric(vertical: 8),
const Divider(thickness: 0.3, height: 1),
Column(
children: AppNavigation.destinations
.map(
(e) => ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
leading: Icon(e.icon, size: 20).paddingAll(2),
title: Text(e.label),
enabled: true,
onTap: () {
AppRouter.instance.goNamed(e.page);
_closeDrawer();
},
),
(e) => _isCollapsed
? Tooltip(
message: e.label,
child: InkWell(
child: Icon(e.icon, size: 20).paddingSymmetric(
horizontal: 28,
vertical: 16,
),
onTap: () {
AppRouter.instance.goNamed(e.page);
_closeDrawer();
},
),
)
: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
leading: Icon(e.icon, size: 20).paddingAll(2),
title: !_isCollapsed ? Text(e.label) : null,
enabled: true,
onTap: () {
AppRouter.instance.goNamed(e.page);
_closeDrawer();
},
),
)
.toList(),
).paddingSymmetric(vertical: 8),
),
const Divider(thickness: 0.3, height: 1),
Expanded(
child: AppNavigationRegions(
child: AppNavigationRegion(
isCollapsed: _isCollapsed,
onSelected: (item) {
_closeDrawer();
},
@ -179,18 +283,63 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
const Divider(thickness: 0.3, height: 1),
Column(
children: [
ListTile(
minTileHeight: 0,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
if (_isCollapsed)
Tooltip(
message: 'settings'.tr,
child: InkWell(
child: const Icon(
Icons.settings,
size: 20,
).paddingSymmetric(
horizontal: 28,
vertical: 10,
),
onTap: () {
AppRouter.instance.pushNamed('settings');
_closeDrawer();
},
),
)
else
ListTile(
minTileHeight: 0,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
leading: const Icon(Icons.settings, size: 20).paddingAll(2),
title: Text('settings'.tr),
onTap: () {
AppRouter.instance.pushNamed('settings');
_closeDrawer();
},
),
if (_isCollapsed)
Tooltip(
message: 'expand'.tr,
child: InkWell(
child: const Icon(Icons.chevron_right, size: 20)
.paddingSymmetric(
horizontal: 28,
vertical: 10,
),
onTap: () {
_expandDrawer();
},
),
)
else
ListTile(
minTileHeight: 0,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
leading:
const Icon(Icons.chevron_left, size: 20).paddingAll(2),
title: Text('collapse'.tr),
onTap: () {
_collapseDrawer();
},
),
leading: const Icon(Icons.settings, size: 20).paddingAll(2),
title: Text('settings'.tr),
onTap: () {
AppRouter.instance.pushNamed('settings');
_closeDrawer();
},
),
],
).paddingOnly(
top: 8,

View File

@ -5,10 +5,15 @@ import 'package:solian/providers/content/channel.dart';
import 'package:solian/router.dart';
import 'package:collection/collection.dart';
class AppNavigationRegions extends StatelessWidget {
class AppNavigationRegion extends StatelessWidget {
final bool isCollapsed;
final Function(Channel item) onSelected;
const AppNavigationRegions({super.key, required this.onSelected});
const AppNavigationRegion({
super.key,
required this.onSelected,
this.isCollapsed = false,
});
void _gotoChannel(Channel item) {
AppRouter.instance.pushReplacementNamed(
@ -25,6 +30,16 @@ class AppNavigationRegions extends StatelessWidget {
Widget _buildEntry(BuildContext context, Channel item) {
const padding = EdgeInsets.symmetric(horizontal: 20);
if (isCollapsed) {
return InkWell(
child: const Icon(Icons.tag_outlined, size: 20).paddingSymmetric(
horizontal: 20,
vertical: 16,
),
onTap: () => _gotoChannel(item),
);
}
return ListTile(
minTileHeight: 0,
leading: const Icon(Icons.tag_outlined),
@ -51,6 +66,27 @@ class AppNavigationRegions extends StatelessWidget {
.where((x) => x.type == 0 && x.realmId != null)
.toList();
if (isCollapsed) {
return CustomScrollView(
slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)),
SliverList.builder(
itemCount:
noRealmGroupChannels.length + hasRealmGroupChannels.length,
itemBuilder: (context, index) {
final element = index >= noRealmGroupChannels.length
? hasRealmGroupChannels[index - noRealmGroupChannels.length]
: noRealmGroupChannels[index];
return Tooltip(
message: element.name,
child: _buildEntry(context, element),
);
},
),
],
);
}
return CustomScrollView(
slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)),

View File

@ -8,94 +8,94 @@ PODS:
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- Firebase/Analytics (10.29.0):
- Firebase/Analytics (11.0.0):
- Firebase/Core
- Firebase/Core (10.29.0):
- Firebase/Core (11.0.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 10.29.0)
- Firebase/CoreOnly (10.29.0):
- FirebaseCore (= 10.29.0)
- Firebase/Crashlytics (10.29.0):
- FirebaseAnalytics (~> 11.0.0)
- Firebase/CoreOnly (11.0.0):
- FirebaseCore (= 11.0.0)
- Firebase/Crashlytics (11.0.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 10.29.0)
- Firebase/Messaging (10.29.0):
- FirebaseCrashlytics (~> 11.0.0)
- Firebase/Messaging (11.0.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 10.29.0)
- firebase_analytics (11.2.1):
- Firebase/Analytics (= 10.29.0)
- FirebaseMessaging (~> 11.0.0)
- firebase_analytics (11.3.0):
- Firebase/Analytics (= 11.0.0)
- firebase_core
- FlutterMacOS
- firebase_core (3.3.0):
- Firebase/CoreOnly (~> 10.29.0)
- firebase_core (3.4.0):
- Firebase/CoreOnly (~> 11.0.0)
- FlutterMacOS
- firebase_crashlytics (4.0.4):
- Firebase/CoreOnly (~> 10.29.0)
- Firebase/Crashlytics (~> 10.29.0)
- firebase_crashlytics (4.1.0):
- Firebase/CoreOnly (~> 11.0.0)
- Firebase/Crashlytics (~> 11.0.0)
- firebase_core
- FlutterMacOS
- firebase_messaging (15.0.4):
- Firebase/CoreOnly (~> 10.29.0)
- Firebase/Messaging (~> 10.29.0)
- firebase_messaging (15.1.0):
- Firebase/CoreOnly (~> 11.0.0)
- Firebase/Messaging (~> 11.0.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)
- GoogleUtilities/UserDefaults (~> 7.8)
- PromisesObjC (~> 2.1)
- FirebaseMessaging (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleDataTransport (~> 9.3)
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
- GoogleUtilities/Environment (~> 7.8)
- 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)
- FirebaseAnalytics (11.0.0):
- FirebaseAnalytics/AdIdSupport (= 11.0.0)
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.0.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseCore (11.0.0):
- FirebaseCoreInternal (~> 11.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreExtension (11.1.0):
- FirebaseCore (~> 11.0)
- FirebaseCoreInternal (11.1.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseCrashlytics (11.0.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfigInterop (~> 11.0)
- FirebaseSessions (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/Environment (~> 8.0)
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- FirebaseInstallations (11.1.0):
- FirebaseCore (~> 11.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.0.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- FirebaseRemoteConfigInterop (11.1.0)
- FirebaseSessions (11.1.0):
- FirebaseCore (~> 11.0)
- FirebaseCoreExtension (~> 11.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- PromisesSwift (~> 2.1)
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
@ -106,56 +106,54 @@ 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)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/AppDelegateSwizzler (7.13.3):
- GoogleAppMeasurement (11.0.0):
- GoogleAppMeasurement/AdIdSupport (= 11.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.0.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.0.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.0.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleUtilities/AppDelegateSwizzler (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (7.13.3):
- GoogleUtilities/Environment (8.0.2):
- GoogleUtilities/Privacy
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Logger (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/MethodSwizzler (7.13.3):
- GoogleUtilities/MethodSwizzler (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/Network (7.13.3):
- GoogleUtilities/Network (8.0.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.13.3)":
- "GoogleUtilities/NSData+zlib (8.0.2)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (7.13.3)
- GoogleUtilities/Reachability (7.13.3):
- GoogleUtilities/Privacy (8.0.2)
- GoogleUtilities/Reachability (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (7.13.3):
- GoogleUtilities/UserDefaults (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- livekit_client (2.2.4):
@ -163,11 +161,17 @@ PODS:
- WebRTC-SDK (= 125.6422.04)
- macos_window_utils (1.0.0):
- FlutterMacOS
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- 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 (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- package_info_plus (0.0.1):
- FlutterMacOS
- pasteboard (0.0.1):
@ -180,6 +184,8 @@ PODS:
- PromisesObjC (= 2.4.0)
- protocol_handler_macos (0.0.1):
- FlutterMacOS
- screen_brightness_macos (0.1.0):
- FlutterMacOS
- share_plus (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
@ -190,9 +196,6 @@ 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)
@ -212,15 +215,18 @@ 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`)
- 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:
@ -272,6 +278,12 @@ 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:
@ -280,6 +292,8 @@ 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
share_plus:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
@ -288,8 +302,6 @@ 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
@ -298,41 +310,44 @@ SPEC CHECKSUMS:
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
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
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9
firebase_analytics: e9ae2af44afdaecf076797dcf9f3cd9c71ad8ac6
firebase_core: ca9bcfb9835e1bcd5ff205e24a541f6bc04e7a35
firebase_crashlytics: 87ec8afd46aeceddb565f7469df437c83fb0e01f
firebase_messaging: c834894f659ce965ba4f93fe5d0dc5d705a3cc88
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
FirebaseCoreExtension: aa5c9779c2d0d39d83f1ceb3fdbafe80c4feecfa
FirebaseCoreInternal: adefedc9a88dbe393c4884640a73ec9e8e790f8c
FirebaseCrashlytics: 745d8f0221fe49c62865391d1bf56f5a12eeec0b
FirebaseInstallations: d0a8fea5a6fa91abc661591cf57c0f0d70863e57
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742
FirebaseRemoteConfigInterop: abf8b1bbc0bf1b84abd22b66746926410bf91a87
FirebaseSessions: 78f137e68dc01ca71606169ba4ac73b98c13752a
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
flutter_webrtc: 2b4e4a2de70a1485836e40fd71a7a94c77d49bd9
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
livekit_client: 95f3b71e6545845aa658a6df0a3a62dcc3471d7c
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
nanopb: 438bc412db1928dac798aa6fd75726007be04262
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_macos: d10a6c01d6373389ffd2278013ab4c47ed6d6daa
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
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

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: b1595874fbc8f7a50da90f5d8f327bb0bfd6a95dc906c390efe991540c3b54aa
sha256: "9371d13b8ee442e3bfc08a24e3a1b3742c839abbfaf5eef11b79c4b862c89bf7"
url: "https://pub.dev"
source: hosted
version: "1.3.40"
version: "1.3.41"
_macros:
dependency: transitive
description: dart
@ -38,6 +38,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.11"
ansicolor:
dependency: transitive
description:
name: ansicolor
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
archive:
dependency: transitive
description:
@ -263,7 +271,7 @@ packages:
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
dependency: "direct main"
description:
name: cross_file
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
@ -278,6 +286,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.5"
csslib:
dependency: transitive
description:
name: csslib
sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
cupertino_icons:
dependency: "direct main"
description:
@ -450,114 +466,114 @@ packages:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "064e5b57b0693305946b7caa6a80ed80a918f46804c247b6cd7ed9cd327df48f"
sha256: "7e032ade38dec2a92f543ba02c5f72f54ffaa095c60d2132b867eab56de3bc73"
url: "https://pub.dev"
source: hosted
version: "11.2.1"
version: "11.3.0"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: d094547c9022c404b5ca39b7209607fc80e75e39d38875f050508fa4346b3e74
sha256: b62a2444767d95067a7e36b1d6e335e0b877968574bbbfb656168c46f2e95a13
url: "https://pub.dev"
source: hosted
version: "4.2.1"
version: "4.2.2"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "06dc023b0144c0df630a56b6262cc9e7d6069fe78148853d97614dbefb6ea923"
sha256: bad44f71f96cfca6c16c9dd4f70b85f123ddca7d5dd698977449fadf298b1782
url: "https://pub.dev"
source: hosted
version: "0.5.9+1"
version: "0.5.9+2"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "3187f4f8e49968573fd7403011dca67ba95aae419bc0d8131500fae160d94f92"
sha256: "06537da27db981947fa535bb91ca120b4e9cb59cb87278dbdde718558cafc9ff"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "3.4.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02"
sha256: f7d7180c7f99babd4b4c517754d41a09a4943a0f7a69b65c894ca5c68ba66315
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.2.1"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: e8d1e22de72cb21cdcfc5eed7acddab3e99cd83f3b317f54f7a96c32f25fd11e
sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88"
url: "https://pub.dev"
source: hosted
version: "2.17.4"
version: "2.17.5"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: "30260e1b8ad1464b41ca4531b44ce63d752daaf2f12c92ca6cdcd82b270abecc"
sha256: "4c9872020c0d97a161362ee6af7000cfdb8666234ddc290a15252ad379bb235a"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
version: "4.1.0"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: a75e1826d92ea4e86e4a753c7b5d64b844a362676fa653185f1581c859186d18
sha256: ede8a199ff03378857d3c8cbb7fa58d37c27bb5a6b75faf8415ff6925dcaae2a
url: "https://pub.dev"
source: hosted
version: "3.6.40"
version: "3.6.41"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "1b0a4f9ecbaf9007771bac152afad738ddfacc4b8431a7591c00829480d99553"
sha256: "29941ba5a3204d80656c0e52103369aa9a53edfd9ceae05a2bb3376f24fda453"
url: "https://pub.dev"
source: hosted
version: "15.0.4"
version: "15.1.0"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: c5a6443e66ae064fe186901d740ee7ce648ca2a6fd0484b8c5e963849ac0fc28
sha256: "26c5370d3a79b15c8032724a68a4741e28f63e1f1a45699c4f0a8ae740aadd72"
url: "https://pub.dev"
source: hosted
version: "4.5.42"
version: "4.5.43"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "232ef63b986467ae5b5577a09c2502b26e2e2aebab5b85e6c966a5ca9b038b89"
sha256: "58276cd5d9e22a9320ef9e5bc358628920f770f93c91221f8b638e8346ed5df4"
url: "https://pub.dev"
source: hosted
version: "3.8.12"
version: "3.8.13"
firebase_performance:
dependency: "direct main"
description:
name: firebase_performance
sha256: "6d17133458b9627f15f278d6f71bebbbce885d393f3462b690e55deeb5c36b90"
sha256: "66666f697ecdcca2616af99f8ccfa74d795e5819c598227f2784fc00b1c6e421"
url: "https://pub.dev"
source: hosted
version: "0.10.0+4"
version: "0.10.0+5"
firebase_performance_platform_interface:
dependency: transitive
description:
name: firebase_performance_platform_interface
sha256: "28dc0a70a3459fe51d1c1be5754803a9a0db0e210322ec7526f6ce42bf6ad83e"
sha256: ceaa026d067347cc6ea11113ba926ae450f56e305c186d1edce78f05983b481a
url: "https://pub.dev"
source: hosted
version: "0.1.4+40"
version: "0.1.4+41"
firebase_performance_web:
dependency: transitive
description:
name: firebase_performance_web
sha256: db91d86b34280f5253d2913945fdd51d7114486584a298a7bedf1c4b2ab08f79
sha256: "6d121cd7e27b63995998dc4039caf0cbf304c2eee6fc6ed9ac7f80860cc0e51c"
url: "https://pub.dev"
source: hosted
version: "0.1.6+12"
version: "0.1.6+13"
fixnum:
dependency: transitive
description:
@ -715,14 +731,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.17+1"
flutter_native_splash:
dependency: "direct dev"
description:
name: flutter_native_splash
sha256: aa06fec78de2190f3db4319dd60fdc8d12b2626e93ef9828633928c2dcaea840
url: "https://pub.dev"
source: hosted
version: "2.4.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
url: "https://pub.dev"
source: hosted
version: "2.0.21"
version: "2.0.22"
flutter_secure_storage:
dependency: "direct main"
description:
@ -787,6 +811,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.0"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev"
source: hosted
version: "2.0.10+1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -821,6 +853,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "10.7.0"
freezed:
dependency: "direct dev"
description:
name: freezed
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
url: "https://pub.dev"
source: hosted
version: "2.5.7"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
url: "https://pub.dev"
source: hosted
version: "2.4.4"
frontend_server_client:
dependency: transitive
description:
@ -837,6 +885,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
gap:
dependency: "direct main"
description:
name: gap
sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d
url: "https://pub.dev"
source: hosted
version: "3.0.1"
get:
dependency: "direct main"
description:
@ -857,10 +913,18 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
url: "https://pub.dev"
source: hosted
version: "14.2.3"
version: "14.2.7"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
url: "https://pub.dev"
source: hosted
version: "6.2.1"
graphs:
dependency: transitive
description:
@ -877,6 +941,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
html:
dependency: transitive
description:
name: html
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev"
source: hosted
version: "0.15.4"
http:
dependency: transitive
description:
@ -945,10 +1017,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56"
sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85
url: "https://pub.dev"
source: hosted
version: "0.8.12+12"
version: "0.8.12+13"
image_picker_for_web:
dependency: transitive
description:
@ -1030,13 +1102,21 @@ packages:
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
dependency: "direct main"
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev"
source: hosted
version: "6.8.0"
leak_tracker:
dependency: transitive
description:
@ -1145,10 +1225,10 @@ packages:
dependency: "direct main"
description:
name: media_kit
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62"
url: "https://pub.dev"
source: hosted
version: "1.1.10+1"
version: "1.1.11"
media_kit_libs_android_video:
dependency: transitive
description:
@ -1185,34 +1265,34 @@ packages:
dependency: "direct main"
description:
name: media_kit_libs_video
sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067"
sha256: "20bb4aefa8fece282b59580e1cd8528117297083a6640c98c2e98cfc96b93288"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5"
media_kit_libs_windows_video:
dependency: transitive
description:
name: media_kit_libs_windows_video
sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122"
sha256: "32654572167825c42c55466f5d08eee23ea11061c84aa91b09d0e0f69bdd0887"
url: "https://pub.dev"
source: hosted
version: "1.0.9"
version: "1.0.10"
media_kit_native_event_loop:
dependency: transitive
description:
name: media_kit_native_event_loop
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e
sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40"
url: "https://pub.dev"
source: hosted
version: "1.0.8"
version: "1.0.9"
media_kit_video:
dependency: "direct main"
description:
name: media_kit_video
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882
sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f"
url: "https://pub.dev"
source: hosted
version: "1.2.4"
version: "1.2.5"
meta:
dependency: transitive
description:
@ -1225,10 +1305,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
nested:
dependency: transitive
description:
@ -1293,6 +1373,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
url: "https://pub.dev"
source: hosted
version: "1.0.1"
path_provider:
dependency: transitive
description:
@ -1369,10 +1457,10 @@ packages:
dependency: transitive
description:
name: permission_handler_html
sha256: d220eb8476b466d58b161e10b3001d93999010a26228a3fb89c4280db1249546
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
url: "https://pub.dev"
source: hosted
version: "0.1.3+1"
version: "0.1.3+2"
permission_handler_platform_interface:
dependency: transitive
description:
@ -1465,10 +1553,10 @@ packages:
dependency: transitive
description:
name: process_run
sha256: c917dfb5f7afad4c7485bc00a4df038621248fce046105020cea276d1a87c820
sha256: "112a77da35be50617ed9e2230df68d0817972f225e7f97ce8336f76b4e601606"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.0"
protobuf:
dependency: transitive
description:
@ -1649,10 +1737,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
shared_preferences_foundation:
dependency: transitive
description:
@ -1730,6 +1818,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
url: "https://pub.dev"
source: hosted
version: "1.3.4"
source_span:
dependency: transitive
description:
@ -1890,6 +1986,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.1"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
universal_platform:
dependency: transitive
description:
@ -1918,10 +2022,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab
url: "https://pub.dev"
source: hosted
version: "6.3.9"
version: "6.3.10"
url_launcher_ios:
dependency: transitive
description:
@ -1974,10 +2078,34 @@ packages:
dependency: "direct main"
description:
name: uuid
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
url: "https://pub.dev"
source: hosted
version: "4.4.2"
version: "4.5.0"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev"
source: hosted
version: "1.1.11+1"
vector_math:
dependency: transitive
description:
@ -1990,10 +2118,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.5"
volume_controller:
dependency: transitive
description:
@ -2100,4 +2228,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.22.0"
flutter: ">=3.24.0"

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App"
publish_to: "none"
version: 1.2.1+19
version: 1.2.1+27
environment:
sdk: ">=3.3.4 <4.0.0"
@ -71,6 +71,12 @@ dependencies:
media_kit: ^1.1.10+1
media_kit_video: ^1.2.4
media_kit_libs_video: ^1.0.4
flutter_svg: ^2.0.10+1
cross_file: ^0.3.4+2
google_fonts: ^6.2.1
freezed_annotation: ^2.4.4
json_annotation: ^4.9.0
gap: ^3.0.1
dev_dependencies:
flutter_test:
@ -80,9 +86,12 @@ dev_dependencies:
flutter_launcher_icons: ^0.13.1
floor_generator: ^1.4.0
build_runner: ^2.1.2
build_runner: ^2.4.12
sqflite_common_ffi: ^2.3.3
sqflite_common_ffi_web: ^0.4.3+1
flutter_native_splash: ^2.4.1
freezed: ^2.5.7
json_serializable: ^6.8.0
flutter:
uses-material-design: true
@ -126,7 +135,7 @@ flutter_launcher_icons:
generate: true
image_path: "assets/icon-w-shadow.png"
background_color: "#ffffff"
theme_color: "#4b5094"
theme_color: "#92645d"
windows:
generate: true
image_path: "assets/icon-w-shadow.png"
@ -135,5 +144,10 @@ flutter_launcher_icons:
generate: true
image_path: "assets/icon-w-shadow.png"
flutter_native_splash:
color: "#fef8f7"
color_dark: "#191110"
image: assets/icon-w-shadow.png
msix_config:
protocol_activation: solink

View File

@ -1,6 +1,4 @@
<!doctype html>
<html>
<head>
<!DOCTYPE html><html><head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
@ -14,24 +12,24 @@
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF" />
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A new Flutter project." />
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="solian" />
<link rel="apple-touch-icon" href="icons/Icon-192.png" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="solian">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Cropper.js -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.js"></script>
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="icon" type="image/png" href="favicon.png">
<!-- Loading styles -->
<style>
@ -68,14 +66,95 @@
</style>
<title>Solian</title>
<link rel="manifest" href="manifest.json" />
</head>
<link rel="manifest" href="manifest.json">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<style id="splash-screen-style">
html {
height: 100%
}
body {
margin: 0;
min-height: 100%;
background-color: #fef8f7;
background-size: 100% 100%;
}
.center {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.contain {
display:block;
width:100%; height:100%;
object-fit: contain;
}
.stretch {
display:block;
width:100%; height:100%;
}
.cover {
display:block;
width:100%; height:100%;
object-fit: cover;
}
.bottom {
position: absolute;
bottom: 0;
left: 50%;
-ms-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.bottomLeft {
position: absolute;
bottom: 0;
left: 0;
}
.bottomRight {
position: absolute;
bottom: 0;
right: 0;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #191110;
}
}
</style>
<script id="splash-screen-script">
function removeSplashFromWeb() {
document.getElementById("splash")?.remove();
document.getElementById("splash-branding")?.remove();
document.body.style.background = "transparent";
}
</script>
</head>
<body>
<picture id="splash">
<source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)">
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
</picture>
<div class="loader-container">
<div class="loader"></div>
</div>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
<script src="flutter_bootstrap.js" async=""></script>
</body></html>

BIN
web/splash/img/dark-1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
web/splash/img/dark-2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
web/splash/img/dark-3x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

BIN
web/splash/img/dark-4x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
web/splash/img/light-1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
web/splash/img/light-2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
web/splash/img/light-3x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

BIN
web/splash/img/light-4x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB