Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc9081b011 | |||
14e8f7b775 | |||
a70e6c7118 | |||
48ca885a2c | |||
09cb340a9d | |||
b6ebd6bef6 | |||
2ec25fd1a2 | |||
bc99865ba8 | |||
f834351ce2 | |||
0f1a02f65b | |||
6ad0a34645 | |||
fdc71475fc | |||
047defebd1 | |||
6148e889aa | |||
1d7affcd84 |
@ -1,17 +1,17 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-feature android:name="android.hardware.camera"/>
|
<uses-feature android:name="android.hardware.camera" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_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.MODIFY_AUDIO_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
|
<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_CONNECT" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/>
|
<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_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
@ -19,31 +19,31 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="Solian"
|
android:label="Solian"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true">
|
android:supportsRtl="true">
|
||||||
<receiver android:exported="false"
|
<receiver android:exported="false"
|
||||||
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"/>
|
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
|
||||||
<receiver android:exported="false"
|
<receiver android:exported="false"
|
||||||
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
|
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
@ -58,29 +58,36 @@
|
|||||||
<data android:host="sn.solsynth.dev" />
|
<data android:host="sn.solsynth.dev" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<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" />
|
<data android:scheme="solink" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.yalantis.ucrop.UCropActivity"
|
android:name="com.yalantis.ucrop.UCropActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||||
|
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2"/>
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
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. -->
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
@ -112,15 +112,19 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
label: 'bsPreparingData',
|
label: 'bsPreparingData',
|
||||||
action: () async {
|
action: () async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
await Future.wait([
|
try {
|
||||||
Get.find<StickerProvider>().refreshAvailableStickers(),
|
await Future.wait([
|
||||||
if (auth.isAuthorized.isTrue)
|
Get.find<StickerProvider>().refreshAvailableStickers(),
|
||||||
Get.find<ChannelProvider>().refreshAvailableChannel(),
|
if (auth.isAuthorized.isTrue)
|
||||||
if (auth.isAuthorized.isTrue)
|
Get.find<ChannelProvider>().refreshAvailableChannel(),
|
||||||
Get.find<RelationshipProvider>().refreshRelativeList(),
|
if (auth.isAuthorized.isTrue)
|
||||||
if (auth.isAuthorized.isTrue)
|
Get.find<RelationshipProvider>().refreshRelativeList(),
|
||||||
Get.find<RealmProvider>().refreshAvailableRealms(),
|
if (auth.isAuthorized.isTrue)
|
||||||
]);
|
Get.find<RealmProvider>().refreshAvailableRealms(),
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
context.showErrorDialog(e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
10
lib/exceptions/request.dart
Normal file
10
lib/exceptions/request.dart
Normal 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}';
|
||||||
|
}
|
6
lib/exceptions/unauthorized.dart
Normal file
6
lib/exceptions/unauthorized.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class UnauthorizedException implements Exception {
|
||||||
|
const UnauthorizedException();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Unauthorized';
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
|
import 'package:solian/exceptions/unauthorized.dart';
|
||||||
|
|
||||||
extension SolianExtenions on BuildContext {
|
extension SolianExtenions on BuildContext {
|
||||||
void showSnackbar(String content, {SnackBarAction? action}) {
|
void showSnackbar(String content, {SnackBarAction? action}) {
|
||||||
@ -48,15 +50,48 @@ extension SolianExtenions on BuildContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showErrorDialog(dynamic exception) {
|
Future<void> showErrorDialog(dynamic exception) {
|
||||||
var stack = StackTrace.current;
|
Widget content = Text(exception.toString().capitalize!);
|
||||||
var stackTrace = '$stack';
|
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>(
|
return showDialog<void>(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
context: this,
|
context: this,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
title: Text('errorHappened'.tr),
|
title: Text('errorHappened'.tr),
|
||||||
content: Text('${exception.toString().capitalize!}\n\nStack Trace: $stackTrace'),
|
content: content,
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () => Navigator.pop(ctx),
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
class NetworkPackage {
|
class NetworkPackage {
|
||||||
String method;
|
String method;
|
||||||
|
String? endpoint;
|
||||||
String? message;
|
String? message;
|
||||||
Map<String, dynamic>? payload;
|
Map<String, dynamic>? payload;
|
||||||
|
|
||||||
NetworkPackage({
|
NetworkPackage({
|
||||||
required this.method,
|
required this.method,
|
||||||
|
this.endpoint,
|
||||||
this.message,
|
this.message,
|
||||||
this.payload,
|
this.payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory NetworkPackage.fromJson(Map<String, dynamic> json) => NetworkPackage(
|
factory NetworkPackage.fromJson(Map<String, dynamic> json) => NetworkPackage(
|
||||||
method: json['w'],
|
method: json['w'],
|
||||||
|
endpoint: json['e'],
|
||||||
message: json['m'],
|
message: json['m'],
|
||||||
payload: json['p'],
|
payload: json['p'],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'w': method,
|
'w': method,
|
||||||
|
'e': endpoint,
|
||||||
'm': message,
|
'm': message,
|
||||||
'p': payload,
|
'p': payload,
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.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/models/account_status.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
@ -33,15 +35,14 @@ class StatusProvider extends GetConnect {
|
|||||||
|
|
||||||
Future<Response> getCurrentStatus() async {
|
Future<Response> getCurrentStatus() async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('auth');
|
||||||
|
|
||||||
return await client.get('/users/me/status');
|
return await client.get('/users/me/status');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Response> getSomeoneStatus(String name) =>
|
Future<Response> getSomeoneStatus(String name) => get('/users/$name/status');
|
||||||
get('/users/$name/status');
|
|
||||||
|
|
||||||
Future<Response> setStatus(
|
Future<Response> setStatus(
|
||||||
String type,
|
String type,
|
||||||
@ -53,7 +54,7 @@ class StatusProvider extends GetConnect {
|
|||||||
DateTime? clearAt,
|
DateTime? clearAt,
|
||||||
}) async {
|
}) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('auth');
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ class StatusProvider extends GetConnect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -82,13 +83,13 @@ class StatusProvider extends GetConnect {
|
|||||||
|
|
||||||
Future<Response> clearStatus() async {
|
Future<Response> clearStatus() async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('auth');
|
||||||
|
|
||||||
final resp = await client.delete('/users/me/status');
|
final resp = await client.delete('/users/me/status');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
@ -7,6 +7,8 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get/get_connect/http/src/request/request.dart';
|
import 'package:get/get_connect/http/src/request/request.dart';
|
||||||
import 'package:solian/controllers/chat_events_controller.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/providers/websocket.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ class AuthProvider extends GetConnect {
|
|||||||
'grant_type': 'refresh_token',
|
'grant_type': 'refresh_token',
|
||||||
});
|
});
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
credentials = TokenSet(
|
credentials = TokenSet(
|
||||||
accessToken: resp.body['access_token'],
|
accessToken: resp.body['access_token'],
|
||||||
@ -128,7 +130,7 @@ class AuthProvider extends GetConnect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> ensureCredentials() async {
|
Future<void> ensureCredentials() async {
|
||||||
if (isAuthorized.isFalse) throw Exception('unauthorized');
|
if (isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
if (credentials == null) await loadCredentials();
|
if (credentials == null) await loadCredentials();
|
||||||
|
|
||||||
if (credentials!.isExpired) {
|
if (credentials!.isExpired) {
|
||||||
@ -158,7 +160,7 @@ class AuthProvider extends GetConnect {
|
|||||||
'password': password,
|
'password': password,
|
||||||
});
|
});
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.body);
|
throw RequestException(resp);
|
||||||
} else if (resp.body['is_finished'] == false) {
|
} else if (resp.body['is_finished'] == false) {
|
||||||
throw RiskyAuthenticateException(resp.body['ticket']['id']);
|
throw RiskyAuthenticateException(resp.body['ticket']['id']);
|
||||||
}
|
}
|
||||||
@ -218,7 +220,7 @@ class AuthProvider extends GetConnect {
|
|||||||
final client = configureClient('auth');
|
final client = configureClient('auth');
|
||||||
final resp = await client.get('/users/me');
|
final resp = await client.get('/users/me');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
userProfile.value = resp.body;
|
userProfile.value = resp.body;
|
||||||
|
@ -2,6 +2,8 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.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:livekit_client/livekit_client.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:solian/models/call.dart';
|
import 'package:solian/models/call.dart';
|
||||||
@ -88,7 +90,7 @@ class ChatCallProvider extends GetxController {
|
|||||||
|
|
||||||
Future<(String, String)> getRoomToken() async {
|
Future<(String, String)> getRoomToken() async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
@ -101,7 +103,7 @@ class ChatCallProvider extends GetxController {
|
|||||||
endpoint = 'wss://${resp.body['endpoint']}';
|
endpoint = 'wss://${resp.body['endpoint']}';
|
||||||
return (token!, endpoint!);
|
return (token!, endpoint!);
|
||||||
} else {
|
} else {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import 'dart:convert';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
|
import 'package:solian/exceptions/unauthorized.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/models/pagination.dart';
|
import 'package:solian/models/pagination.dart';
|
||||||
@ -89,7 +91,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
Map<String, dynamic>? metadata,
|
Map<String, dynamic>? metadata,
|
||||||
) async {
|
) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
final client = auth.configureClient(
|
final client = auth.configureClient(
|
||||||
'uc',
|
'uc',
|
||||||
@ -118,7 +120,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
});
|
});
|
||||||
final resp = await client.post('/attachments', payload);
|
final resp = await client.post('/attachments', payload);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Attachment.fromJson(resp.body);
|
return Attachment.fromJson(resp.body);
|
||||||
@ -131,7 +133,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
Map<String, dynamic>? metadata,
|
Map<String, dynamic>? metadata,
|
||||||
) async {
|
) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
final client = auth.configureClient('uc');
|
final client = auth.configureClient('uc');
|
||||||
|
|
||||||
@ -156,7 +158,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
});
|
});
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AttachmentPlaceholder.fromJson(resp.body);
|
return AttachmentPlaceholder.fromJson(resp.body);
|
||||||
@ -169,7 +171,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
String cid,
|
String cid,
|
||||||
) async {
|
) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
final client = auth.configureClient(
|
final client = auth.configureClient(
|
||||||
'uc',
|
'uc',
|
||||||
@ -181,7 +183,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
});
|
});
|
||||||
final resp = await client.post('/attachments/multipart/$rid/$cid', payload);
|
final resp = await client.post('/attachments/multipart/$rid/$cid', payload);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Attachment.fromJson(resp.body);
|
return Attachment.fromJson(resp.body);
|
||||||
@ -193,7 +195,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
bool isMature = false,
|
bool isMature = false,
|
||||||
}) async {
|
}) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
final client = auth.configureClient('files');
|
final client = auth.configureClient('files');
|
||||||
|
|
||||||
@ -203,7 +205,7 @@ class AttachmentProvider extends GetConnect {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -211,13 +213,13 @@ class AttachmentProvider extends GetConnect {
|
|||||||
|
|
||||||
Future<Response> deleteAttachment(int id) async {
|
Future<Response> deleteAttachment(int id) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
final client = auth.configureClient('files');
|
final client = auth.configureClient('files');
|
||||||
|
|
||||||
var resp = await client.delete('/attachments/$id');
|
var resp = await client.delete('/attachments/$id');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.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/models/channel.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/widgets/account/relative_select.dart';
|
import 'package:solian/widgets/account/relative_select.dart';
|
||||||
@ -16,7 +18,7 @@ class ChannelProvider extends GetxController {
|
|||||||
|
|
||||||
Future<void> refreshAvailableChannel() async {
|
Future<void> refreshAvailableChannel() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
final resp = await listAvailableChannel();
|
final resp = await listAvailableChannel();
|
||||||
@ -29,13 +31,13 @@ class ChannelProvider extends GetxController {
|
|||||||
|
|
||||||
Future<Response> getChannel(String alias, {String realm = 'global'}) async {
|
Future<Response> getChannel(String alias, {String realm = 'global'}) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
final resp = await client.get('/channels/$realm/$alias');
|
final resp = await client.get('/channels/$realm/$alias');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -44,13 +46,13 @@ class ChannelProvider extends GetxController {
|
|||||||
Future<Response> getMyChannelProfile(String alias,
|
Future<Response> getMyChannelProfile(String alias,
|
||||||
{String realm = 'global'}) async {
|
{String realm = 'global'}) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
final resp = await client.get('/channels/$realm/$alias/me');
|
final resp = await client.get('/channels/$realm/$alias/me');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -59,7 +61,7 @@ class ChannelProvider extends GetxController {
|
|||||||
Future<Response?> getChannelOngoingCall(String alias,
|
Future<Response?> getChannelOngoingCall(String alias,
|
||||||
{String realm = 'global'}) async {
|
{String realm = 'global'}) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ class ChannelProvider extends GetxController {
|
|||||||
if (resp.statusCode == 404) {
|
if (resp.statusCode == 404) {
|
||||||
return null;
|
return null;
|
||||||
} else if (resp.statusCode != 200) {
|
} else if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -75,13 +77,13 @@ class ChannelProvider extends GetxController {
|
|||||||
|
|
||||||
Future<Response> listChannel({String scope = 'global'}) async {
|
Future<Response> listChannel({String scope = 'global'}) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
final resp = await client.get('/channels/$scope');
|
final resp = await client.get('/channels/$scope');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -89,13 +91,13 @@ class ChannelProvider extends GetxController {
|
|||||||
|
|
||||||
Future<Response> listAvailableChannel({String realm = 'global'}) async {
|
Future<Response> listAvailableChannel({String realm = 'global'}) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
final resp = await client.get('/channels/$realm/me/available');
|
final resp = await client.get('/channels/$realm/me/available');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -103,13 +105,13 @@ class ChannelProvider extends GetxController {
|
|||||||
|
|
||||||
Future<Response> createChannel(String scope, dynamic payload) async {
|
Future<Response> createChannel(String scope, dynamic payload) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
final resp = await client.post('/channels/$scope', payload);
|
final resp = await client.post('/channels/$scope', payload);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -118,7 +120,7 @@ class ChannelProvider extends GetxController {
|
|||||||
Future<Response?> createDirectChannel(
|
Future<Response?> createDirectChannel(
|
||||||
BuildContext context, String scope) async {
|
BuildContext context, String scope) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
final related = await showModalBottomSheet(
|
final related = await showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
@ -141,7 +143,7 @@ class ChannelProvider extends GetxController {
|
|||||||
'is_encrypted': false,
|
'is_encrypted': false,
|
||||||
});
|
});
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -149,13 +151,13 @@ class ChannelProvider extends GetxController {
|
|||||||
|
|
||||||
Future<Response> updateChannel(String scope, int id, dynamic payload) async {
|
Future<Response> updateChannel(String scope, int id, dynamic payload) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('messaging');
|
||||||
|
|
||||||
final resp = await client.put('/channels/$scope/$id', payload);
|
final resp = await client.put('/channels/$scope/$id', payload);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'package:get/get.dart';
|
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/providers/auth.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ class PostProvider extends GetConnect {
|
|||||||
: '/recommendations/$channel?${queries.join('&')}',
|
: '/recommendations/$channel?${queries.join('&')}',
|
||||||
);
|
);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.body);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -36,7 +38,7 @@ class PostProvider extends GetConnect {
|
|||||||
|
|
||||||
Future<Response> listDraft(int page) async {
|
Future<Response> listDraft(int page) async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
final queries = [
|
final queries = [
|
||||||
'take=${10}',
|
'take=${10}',
|
||||||
@ -45,7 +47,7 @@ class PostProvider extends GetConnect {
|
|||||||
final client = auth.configureClient('interactive');
|
final client = auth.configureClient('interactive');
|
||||||
final resp = await client.get('/posts/drafts?${queries.join('&')}');
|
final resp = await client.get('/posts/drafts?${queries.join('&')}');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.body);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -63,7 +65,7 @@ class PostProvider extends GetConnect {
|
|||||||
];
|
];
|
||||||
final resp = await get('/posts?${queries.join('&')}');
|
final resp = await get('/posts?${queries.join('&')}');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.body);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -72,7 +74,7 @@ class PostProvider extends GetConnect {
|
|||||||
Future<Response> listPostReplies(String alias, int page) async {
|
Future<Response> listPostReplies(String alias, int page) async {
|
||||||
final resp = await get('/posts/$alias/replies?take=${10}&offset=$page');
|
final resp = await get('/posts/$alias/replies?take=${10}&offset=$page');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.body);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -81,7 +83,7 @@ class PostProvider extends GetConnect {
|
|||||||
Future<Response> getPost(String alias) async {
|
Future<Response> getPost(String alias) async {
|
||||||
final resp = await get('/posts/$alias');
|
final resp = await get('/posts/$alias');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.body);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -90,7 +92,7 @@ class PostProvider extends GetConnect {
|
|||||||
Future<Response> getArticle(String alias) async {
|
Future<Response> getArticle(String alias) async {
|
||||||
final resp = await get('/articles/$alias');
|
final resp = await get('/articles/$alias');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.body);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'package:get/get.dart';
|
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/models/realm.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
|
|
||||||
@ -8,7 +10,7 @@ class RealmProvider extends GetxController {
|
|||||||
|
|
||||||
Future<void> refreshAvailableRealms() async {
|
Future<void> refreshAvailableRealms() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) throw Exception('unauthorized');
|
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
final resp = await listAvailableRealm();
|
final resp = await listAvailableRealm();
|
||||||
@ -21,13 +23,13 @@ class RealmProvider extends GetxController {
|
|||||||
|
|
||||||
Future<Response> getRealm(String alias) async {
|
Future<Response> getRealm(String alias) async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('auth');
|
||||||
|
|
||||||
final resp = await client.get('/realms/$alias');
|
final resp = await client.get('/realms/$alias');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -35,13 +37,13 @@ class RealmProvider extends GetxController {
|
|||||||
|
|
||||||
Future<Response> listAvailableRealm() async {
|
Future<Response> listAvailableRealm() async {
|
||||||
final AuthProvider auth = Get.find();
|
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 client = auth.configureClient('auth');
|
||||||
|
|
||||||
final resp = await client.get('/realms/me/available');
|
final resp = await client.get('/realms/me/available');
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:floor/floor.dart';
|
import 'package:floor/floor.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/models/event.dart';
|
import 'package:solian/models/event.dart';
|
||||||
import 'package:solian/models/pagination.dart';
|
import 'package:solian/models/pagination.dart';
|
||||||
@ -29,20 +30,20 @@ Future<Event?> getRemoteEvent(int id, Channel channel, String scope) async {
|
|||||||
if (resp.statusCode == 404) {
|
if (resp.statusCode == 404) {
|
||||||
return null;
|
return null;
|
||||||
} else if (resp.statusCode != 200) {
|
} else if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event.fromJson(resp.body);
|
return Event.fromJson(resp.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(List<Event>, int)?> getRemoteEvents(
|
Future<(List<Event>, int)?> getRemoteEvents(
|
||||||
Channel channel,
|
Channel channel,
|
||||||
String scope, {
|
String scope, {
|
||||||
required int remainDepth,
|
required int remainDepth,
|
||||||
bool Function(List<Event> items)? onBrake,
|
bool Function(List<Event> items)? onBrake,
|
||||||
take = 10,
|
take = 10,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
}) async {
|
}) async {
|
||||||
if (remainDepth <= 0) {
|
if (remainDepth <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ Future<(List<Event>, int)?> getRemoteEvents(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
final PaginationResult response = PaginationResult.fromJson(resp.body);
|
final PaginationResult response = PaginationResult.fromJson(resp.body);
|
||||||
@ -69,13 +70,13 @@ Future<(List<Event>, int)?> getRemoteEvents(
|
|||||||
}
|
}
|
||||||
|
|
||||||
final expandResult = (await getRemoteEvents(
|
final expandResult = (await getRemoteEvents(
|
||||||
channel,
|
channel,
|
||||||
scope,
|
scope,
|
||||||
remainDepth: remainDepth - 1,
|
remainDepth: remainDepth - 1,
|
||||||
take: take,
|
take: take,
|
||||||
offset: offset + result.length,
|
offset: offset + result.length,
|
||||||
))
|
))
|
||||||
?.$1 ??
|
?.$1 ??
|
||||||
List.empty();
|
List.empty();
|
||||||
|
|
||||||
return ([...result, ...expandResult], response.count);
|
return ([...result, ...expandResult], response.count);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
import 'package:solian/models/relations.dart';
|
import 'package:solian/models/relations.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
@ -42,7 +43,7 @@ class RelationshipProvider extends GetxController {
|
|||||||
final client = auth.configureClient('auth');
|
final client = auth.configureClient('auth');
|
||||||
final resp = await client.post('/users/me/relations?related=$username', {});
|
final resp = await client.post('/users/me/relations?related=$username', {});
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -57,7 +58,7 @@ class RelationshipProvider extends GetxController {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
@ -71,7 +72,7 @@ class RelationshipProvider extends GetxController {
|
|||||||
{'status': status},
|
{'status': status},
|
||||||
);
|
);
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
@ -6,6 +6,7 @@ import 'dart:io';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exceptions/request.dart';
|
||||||
import 'package:solian/models/notification.dart';
|
import 'package:solian/models/notification.dart';
|
||||||
import 'package:solian/models/packet.dart';
|
import 'package:solian/models/packet.dart';
|
||||||
import 'package:solian/models/pagination.dart';
|
import 'package:solian/models/pagination.dart';
|
||||||
@ -50,31 +51,31 @@ class WebSocketProvider extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final AuthProvider auth = Get.find();
|
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 {
|
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);
|
websocket = WebSocketChannel.connect(uri);
|
||||||
await websocket?.ready;
|
await websocket?.ready;
|
||||||
} catch (e) {
|
listen();
|
||||||
|
|
||||||
|
isConnected.value = true;
|
||||||
|
} catch (err) {
|
||||||
|
log('Unable connect dealer via websocket... $err');
|
||||||
if (!noRetry) {
|
if (!noRetry) {
|
||||||
await auth.refreshCredentials();
|
await auth.refreshCredentials();
|
||||||
return connect(noRetry: true);
|
return connect(noRetry: true);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
isConnecting.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
listen();
|
|
||||||
|
|
||||||
isConnected.value = true;
|
|
||||||
isConnecting.value = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void disconnect() {
|
void disconnect() {
|
||||||
@ -87,6 +88,7 @@ class WebSocketProvider extends GetxController {
|
|||||||
websocket?.stream.listen(
|
websocket?.stream.listen(
|
||||||
(event) {
|
(event) {
|
||||||
final packet = NetworkPackage.fromJson(jsonDecode(event));
|
final packet = NetworkPackage.fromJson(jsonDecode(event));
|
||||||
|
log('Websocket incoming message: ${packet.method} ${packet.message}');
|
||||||
stream.sink.add(packet);
|
stream.sink.add(packet);
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
@ -147,8 +149,8 @@ class WebSocketProvider extends GetxController {
|
|||||||
'device_token': token,
|
'device_token': token,
|
||||||
'device_id': deviceUuid,
|
'device_id': deviceUuid,
|
||||||
});
|
});
|
||||||
if (resp.statusCode != 200) {
|
if (resp.statusCode != 200 && resp.statusCode != 400) {
|
||||||
throw Exception(resp.bodyString);
|
throw RequestException(resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
|||||||
final _descriptionController = TextEditingController();
|
final _descriptionController = TextEditingController();
|
||||||
final _birthdayController = TextEditingController();
|
final _birthdayController = TextEditingController();
|
||||||
|
|
||||||
int? _avatar;
|
String? _avatar;
|
||||||
int? _banner;
|
String? _banner;
|
||||||
DateTime? _birthday;
|
DateTime? _birthday;
|
||||||
|
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
@ -109,11 +109,11 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
|||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final AttachmentProvider provider = Get.find();
|
final AttachmentProvider attach = Get.find();
|
||||||
|
|
||||||
Attachment? attachResult;
|
Attachment? attachResult;
|
||||||
try {
|
try {
|
||||||
attachResult = await provider.createAttachmentDirectly(
|
attachResult = await attach.createAttachmentDirectly(
|
||||||
await file.readAsBytes(),
|
await file.readAsBytes(),
|
||||||
file.path,
|
file.path,
|
||||||
'avatar',
|
'avatar',
|
||||||
@ -129,7 +129,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
|||||||
|
|
||||||
final resp = await client.put(
|
final resp = await client.put(
|
||||||
'/users/me/$position',
|
'/users/me/$position',
|
||||||
{'attachment': attachResult.id},
|
{'attachment': attachResult.rid},
|
||||||
);
|
);
|
||||||
if (resp.statusCode == 200) {
|
if (resp.statusCode == 200) {
|
||||||
_syncWidget();
|
_syncWidget();
|
||||||
|
@ -13,8 +13,13 @@ import 'package:livekit_client/livekit_client.dart' as livekit;
|
|||||||
|
|
||||||
class CallScreen extends StatefulWidget {
|
class CallScreen extends StatefulWidget {
|
||||||
final bool hideAppBar;
|
final bool hideAppBar;
|
||||||
|
final bool isExpandable;
|
||||||
|
|
||||||
const CallScreen({super.key, this.hideAppBar = false});
|
const CallScreen({
|
||||||
|
super.key,
|
||||||
|
this.hideAppBar = false,
|
||||||
|
this.isExpandable = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CallScreen> createState() => _CallScreenState();
|
State<CallScreen> createState() => _CallScreenState();
|
||||||
@ -308,13 +313,24 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
IconButton(
|
Row(
|
||||||
icon: _layoutMode == 0
|
children: [
|
||||||
? const Icon(Icons.view_list)
|
if (widget.isExpandable)
|
||||||
: const Icon(Icons.grid_view),
|
IconButton(
|
||||||
onPressed: () {
|
icon: const Icon(Icons.fullscreen),
|
||||||
_switchLayout();
|
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),
|
).paddingOnly(left: 20, right: 16),
|
||||||
|
@ -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/call/chat_call_action.dart';
|
||||||
import 'package:solian/widgets/chat/chat_event_list.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_message_input.dart';
|
||||||
|
import 'package:solian/widgets/chat/chat_typing_indicator.dart';
|
||||||
import 'package:solian/widgets/current_state_action.dart';
|
import 'package:solian/widgets/current_state_action.dart';
|
||||||
|
|
||||||
class ChannelChatScreen extends StatefulWidget {
|
class ChannelChatScreen extends StatefulWidget {
|
||||||
@ -103,12 +104,18 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
|||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<ChannelMember> _typingUsers = List.empty(growable: true);
|
||||||
|
final Map<int, Timer> _typingInactiveTimer = {};
|
||||||
|
|
||||||
void _listenMessages() {
|
void _listenMessages() {
|
||||||
final WebSocketProvider provider = Get.find();
|
final WebSocketProvider ws = Get.find();
|
||||||
_subscription = provider.stream.stream.listen((event) {
|
_subscription = ws.stream.stream.listen((event) {
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
case 'events.new':
|
case 'events.new':
|
||||||
final payload = Event.fromJson(event.payload!);
|
final payload = Event.fromJson(event.payload!);
|
||||||
|
final typingIdx =
|
||||||
|
_typingUsers.indexWhere((x) => x.id == payload.senderId);
|
||||||
|
if (typingIdx != -1) _typingUsers.removeAt(typingIdx);
|
||||||
_chatController.receiveEvent(payload);
|
_chatController.receiveEvent(payload);
|
||||||
break;
|
break;
|
||||||
case 'calls.new':
|
case 'calls.new':
|
||||||
@ -123,6 +130,25 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
|||||||
setState(() => _ongoingCall = null);
|
setState(() => _ongoingCall = null);
|
||||||
}
|
}
|
||||||
break;
|
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) {
|
switch (state) {
|
||||||
case AppLifecycleState.resumed:
|
case AppLifecycleState.resumed:
|
||||||
if (_isOutOfSyncSince == null) break;
|
if (_isOutOfSyncSince == null) break;
|
||||||
if (DateTime.now().difference(_isOutOfSyncSince!).inSeconds < 60) break;
|
if (DateTime.now().difference(_isOutOfSyncSince!).inSeconds < 30) break;
|
||||||
_keepUpdateWithServer();
|
_keepUpdateWithServer();
|
||||||
break;
|
break;
|
||||||
case AppLifecycleState.paused:
|
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(() {
|
Obx(() {
|
||||||
if (_chatController.isLoading.isTrue) {
|
if (_chatController.isLoading.isTrue) {
|
||||||
return const LinearProgressIndicator().animate().slideY();
|
return const LinearProgressIndicator().animate().slideY();
|
||||||
@ -300,23 +306,28 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
|||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
|
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: ChatMessageInput(
|
child: Column(
|
||||||
edit: _messageToEditing,
|
children: [
|
||||||
reply: _messageToReplying,
|
ChatTypingIndicator(users: _typingUsers),
|
||||||
realm: widget.realm,
|
ChatMessageInput(
|
||||||
placeholder: placeholder,
|
edit: _messageToEditing,
|
||||||
channel: _channel!,
|
reply: _messageToReplying,
|
||||||
onSent: (Event item) {
|
realm: widget.realm,
|
||||||
setState(() {
|
placeholder: placeholder,
|
||||||
_chatController.addPendingEvent(item);
|
channel: _channel!,
|
||||||
});
|
onSent: (Event item) {
|
||||||
},
|
setState(() {
|
||||||
onReset: () {
|
_chatController.addPendingEvent(item);
|
||||||
setState(() {
|
});
|
||||||
_messageToReplying = null;
|
},
|
||||||
_messageToEditing = null;
|
onReset: () {
|
||||||
});
|
setState(() {
|
||||||
},
|
_messageToReplying = null;
|
||||||
|
_messageToEditing = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -331,7 +342,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
|||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
VerticalDivider(width: 0.3, thickness: 0.3),
|
VerticalDivider(width: 0.3, thickness: 0.3),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CallScreen(hideAppBar: true),
|
child: CallScreen(
|
||||||
|
hideAppBar: true,
|
||||||
|
isExpandable: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
@ -346,6 +360,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
for (var timer in _typingInactiveTimer.values) {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
_subscription?.cancel();
|
_subscription?.cancel();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -80,13 +80,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final ChannelProvider provider = Get.find();
|
final ChannelProvider channels = Get.find();
|
||||||
provider
|
channels
|
||||||
.createDirectChannel(context, 'global')
|
.createDirectChannel(context, 'global')
|
||||||
.then((resp) {
|
.then((resp) {
|
||||||
if (resp != null) {
|
if (resp != null) {
|
||||||
_channels.refreshAvailableChannel();
|
_channels.refreshAvailableChannel();
|
||||||
}
|
}
|
||||||
|
}).catchError((e) {
|
||||||
|
context.showErrorDialog(e);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -125,6 +127,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
noCategory: true,
|
noCategory: true,
|
||||||
channels: _channels.directChannels,
|
channels: _channels.directChannels,
|
||||||
selfId: selfId,
|
selfId: selfId,
|
||||||
|
useReplace: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -41,6 +41,8 @@ const i18nEnglish = {
|
|||||||
'openInBrowser': 'Open in browser',
|
'openInBrowser': 'Open in browser',
|
||||||
'notification': 'Notification',
|
'notification': 'Notification',
|
||||||
'errorHappened': 'An error occurred',
|
'errorHappened': 'An error occurred',
|
||||||
|
'errorHappenedUnauthorized':
|
||||||
|
'Unauthorized request, please sign in or try resign in.',
|
||||||
'forgotPassword': 'Forgot password',
|
'forgotPassword': 'Forgot password',
|
||||||
'email': 'Email',
|
'email': 'Email',
|
||||||
'username': 'Username',
|
'username': 'Username',
|
||||||
@ -381,4 +383,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.',
|
'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',
|
'messageHistoryWipe': 'Wipe local message history',
|
||||||
'unknown': 'Unknown',
|
'unknown': 'Unknown',
|
||||||
|
'collapse': 'Collapse',
|
||||||
|
'expand': 'Expand',
|
||||||
|
'typingMessage': '@user are typing...',
|
||||||
};
|
};
|
||||||
|
@ -41,6 +41,12 @@ const i18nSimplifiedChinese = {
|
|||||||
'openInBrowser': '在浏览器中打开',
|
'openInBrowser': '在浏览器中打开',
|
||||||
'notification': '通知',
|
'notification': '通知',
|
||||||
'errorHappened': '发生错误了',
|
'errorHappened': '发生错误了',
|
||||||
|
'errorHappenedUnauthorized': '未经授权的请求,请登录或尝试重新登录。',
|
||||||
|
'errorHappenedRequestBad': '请求错误,服务器拒绝处理该请求,请检查您的请求数据。',
|
||||||
|
'errorHappenedRequestForbidden': '请求错误,权限不足。',
|
||||||
|
'errorHappenedRequestNotFound': '请求错误,请求的数据不存在。',
|
||||||
|
'errorHappenedRequestConnection': '网络请求失败,请检查连接状态与服务状态后再试。',
|
||||||
|
'errorHappenedRequestUnknown': '请求错误,类型未知,请将本提示完整截图提交反馈。',
|
||||||
'forgotPassword': '忘记密码',
|
'forgotPassword': '忘记密码',
|
||||||
'email': '邮件地址',
|
'email': '邮件地址',
|
||||||
'username': '用户名',
|
'username': '用户名',
|
||||||
@ -347,4 +353,7 @@ const i18nSimplifiedChinese = {
|
|||||||
'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。',
|
'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。',
|
||||||
'messageHistoryWipe': '清除消息记录',
|
'messageHistoryWipe': '清除消息记录',
|
||||||
'unknown': '未知',
|
'unknown': '未知',
|
||||||
|
'collapse': '折叠',
|
||||||
|
'expand': '展开',
|
||||||
|
'typingMessage': '@user 正在输入中…',
|
||||||
};
|
};
|
||||||
|
@ -28,11 +28,11 @@ class _AttachmentAttrEditorDialogState
|
|||||||
bool _isMature = false;
|
bool _isMature = false;
|
||||||
|
|
||||||
Future<Attachment?> _updateAttachment() async {
|
Future<Attachment?> _updateAttachment() async {
|
||||||
final AttachmentProvider provider = Get.find();
|
final AttachmentProvider attach = Get.find();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
final resp = await provider.updateAttachment(
|
final resp = await attach.updateAttachment(
|
||||||
widget.item.id,
|
widget.item.id,
|
||||||
_altController.value.text,
|
_altController.value.text,
|
||||||
isMature: _isMature,
|
isMature: _isMature,
|
||||||
|
@ -147,17 +147,21 @@ class _AttachmentItemImage extends StatelessWidget {
|
|||||||
errorWidget: (context, url, error) {
|
errorWidget: (context, url, error) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: Center(
|
child: Column(
|
||||||
child: const Icon(Icons.close, size: 32)
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
.animate(onPlay: (e) => e.repeat(reverse: true))
|
children: [
|
||||||
.fade(duration: 500.ms),
|
const Icon(Icons.close, size: 32)
|
||||||
|
.animate(onPlay: (e) => e.repeat(reverse: true))
|
||||||
|
.fade(duration: 500.ms),
|
||||||
|
Text(error.toString()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Image.network(
|
Image.network(
|
||||||
ServiceFinder.buildUrl('files', '/attachments/${item.id}'),
|
ServiceFinder.buildUrl('files', '/attachments/${item.rid}'),
|
||||||
fit: fit,
|
fit: fit,
|
||||||
loadingBuilder: (BuildContext context, Widget child,
|
loadingBuilder: (BuildContext context, Widget child,
|
||||||
ImageChunkEvent? loadingProgress) {
|
ImageChunkEvent? loadingProgress) {
|
||||||
@ -174,10 +178,14 @@ class _AttachmentItemImage extends StatelessWidget {
|
|||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: Center(
|
child: Column(
|
||||||
child: const Icon(Icons.close, size: 32)
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
.animate(onPlay: (e) => e.repeat(reverse: true))
|
children: [
|
||||||
.fade(duration: 500.ms),
|
const Icon(Icons.close, size: 32)
|
||||||
|
.animate(onPlay: (e) => e.repeat(reverse: true))
|
||||||
|
.fade(duration: 500.ms),
|
||||||
|
Text(error.toString()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -132,6 +132,9 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
_getMetadataList();
|
_getMetadataList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color get _unFocusColor =>
|
||||||
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.attachmentsId.isEmpty) {
|
if (widget.attachmentsId.isEmpty) {
|
||||||
@ -139,12 +142,24 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_isLoading) {
|
if (_isLoading) {
|
||||||
return Container(
|
return Row(
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
Icon(
|
||||||
),
|
Icons.file_copy,
|
||||||
child: const LinearProgressIndicator(),
|
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) {
|
if (widget.isColumn) {
|
||||||
@ -157,8 +172,11 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
final element = _attachmentsMeta[idx];
|
final element = _attachmentsMeta[idx];
|
||||||
idx++;
|
idx++;
|
||||||
if (element == null) return const SizedBox();
|
if (element == null) return const SizedBox();
|
||||||
double ratio = element.metadata!['ratio']?.toDouble() ?? 16 / 9;
|
double ratio = element.metadata?['ratio']?.toDouble() ?? 16 / 9;
|
||||||
return Container(
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
),
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: widget.columnMaxWidth,
|
maxWidth: widget.columnMaxWidth,
|
||||||
maxHeight: 640,
|
maxHeight: 640,
|
||||||
@ -204,6 +222,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
final element = _attachmentsMeta[idx];
|
final element = _attachmentsMeta[idx];
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1,
|
width: 1,
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.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/account.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/models/event.dart';
|
import 'package:solian/models/event.dart';
|
||||||
|
import 'package:solian/models/packet.dart';
|
||||||
import 'package:solian/platform.dart';
|
import 'package:solian/platform.dart';
|
||||||
import 'package:solian/providers/attachment_uploader.dart';
|
import 'package:solian/providers/attachment_uploader.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/stickers.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/account/account_avatar.dart';
|
||||||
import 'package:solian/widgets/attachments/attachment_editor.dart';
|
import 'package:solian/widgets/attachments/attachment_editor.dart';
|
||||||
import 'package:solian/widgets/chat/chat_event.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() {
|
void _resetInput() {
|
||||||
if (widget.onReset != null) widget.onReset!();
|
if (widget.onReset != null) widget.onReset!();
|
||||||
_editTo = null;
|
_editTo = null;
|
||||||
@ -239,7 +274,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
var insertText = '';
|
var insertText = '';
|
||||||
|
|
||||||
if (suggestion.type == 'emotes') {
|
if (suggestion.type == 'emotes') {
|
||||||
insertText = suggestion.content;
|
insertText = '${suggestion.content} ';
|
||||||
startText = replaceText.replaceFirstMapped(
|
startText = replaceText.replaceFirstMapped(
|
||||||
RegExp(r':(?:([-\w]+)~)?([-\w]+)$'),
|
RegExp(r':(?:([-\w]+)~)?([-\w]+)$'),
|
||||||
(Match m) => insertText,
|
(Match m) => insertText,
|
||||||
@ -247,7 +282,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (suggestion.type == 'users') {
|
if (suggestion.type == 'users') {
|
||||||
insertText = suggestion.content;
|
insertText = '${suggestion.content} ';
|
||||||
startText = replaceText.replaceFirstMapped(
|
startText = replaceText.replaceFirstMapped(
|
||||||
RegExp(r'(?:\s|^)@([-\w]+)$'),
|
RegExp(r'(?:\s|^)@([-\w]+)$'),
|
||||||
(Match m) => insertText,
|
(Match m) => insertText,
|
||||||
@ -269,6 +304,20 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_textController.addListener(_pingEnterMessageStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textController.removeListener(_pingEnterMessageStatus);
|
||||||
|
_textController.dispose();
|
||||||
|
_typingNotifyTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final notifyBannerActions = [
|
final notifyBannerActions = [
|
||||||
|
58
lib/widgets/chat/chat_typing_indicator.dart
Normal file
58
lib/widgets/chat/chat_typing_indicator.dart
Normal 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -17,8 +18,36 @@ class LinkExpansion extends StatelessWidget {
|
|||||||
return SvgPicture.network(url, width: width, height: height);
|
return SvgPicture.network(url, width: width, height: height);
|
||||||
}
|
}
|
||||||
return PlatformInfo.canCacheImage
|
return PlatformInfo.canCacheImage
|
||||||
? CachedNetworkImage(imageUrl: url, width: width, height: height)
|
? CachedNetworkImage(
|
||||||
: Image.network(url, width: width, height: height);
|
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
|
@override
|
||||||
|
@ -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/account/account_status_action.dart';
|
||||||
import 'package:solian/widgets/navigation/app_navigation.dart';
|
import 'package:solian/widgets/navigation/app_navigation.dart';
|
||||||
import 'package:badges/badges.dart' as badges;
|
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 {
|
class AppNavigationDrawer extends StatefulWidget {
|
||||||
final String? routeName;
|
final String? routeName;
|
||||||
@ -24,7 +24,23 @@ class AppNavigationDrawer extends StatefulWidget {
|
|||||||
State<AppNavigationDrawer> createState() => _AppNavigationDrawerState();
|
State<AppNavigationDrawer> createState() => _AppNavigationDrawerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
class _AppNavigationDrawerState extends State<AppNavigationDrawer>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
bool _isCollapsed = false;
|
||||||
|
|
||||||
|
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;
|
AccountStatus? _accountStatus;
|
||||||
|
|
||||||
Future<void> _getStatus() async {
|
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() {
|
void _closeDrawer() {
|
||||||
|
_autoResize();
|
||||||
rootScaffoldKey.currentState!.closeDrawer();
|
rootScaffoldKey.currentState!.closeDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _autoResize() {
|
||||||
|
if (SolianTheme.isExtraLargeScreen(context)) {
|
||||||
|
_expandDrawer();
|
||||||
|
} else if (SolianTheme.isLargeScreen(context)) {
|
||||||
|
_collapseDrawer();
|
||||||
|
} else {
|
||||||
|
_drawerAnimationController.value = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_getStatus();
|
_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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final AuthProvider auth = Get.find();
|
return AnimatedBuilder(
|
||||||
|
animation: _drawerAnimation,
|
||||||
return Drawer(
|
builder: (context, child) {
|
||||||
backgroundColor:
|
return Drawer(
|
||||||
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
|
width: _drawerAnimation.value,
|
||||||
|
backgroundColor:
|
||||||
|
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Obx(() {
|
_buildUserInfo().paddingSymmetric(vertical: 8),
|
||||||
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),
|
|
||||||
const Divider(thickness: 0.3, height: 1),
|
const Divider(thickness: 0.3, height: 1),
|
||||||
Column(
|
Column(
|
||||||
children: AppNavigation.destinations
|
children: AppNavigation.destinations
|
||||||
.map(
|
.map(
|
||||||
(e) => ListTile(
|
(e) => _isCollapsed
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
? Tooltip(
|
||||||
horizontal: 20,
|
message: e.label,
|
||||||
),
|
child: InkWell(
|
||||||
leading: Icon(e.icon, size: 20).paddingAll(2),
|
child: Icon(e.icon, size: 20).paddingSymmetric(
|
||||||
title: Text(e.label),
|
horizontal: 28,
|
||||||
enabled: true,
|
vertical: 16,
|
||||||
onTap: () {
|
),
|
||||||
AppRouter.instance.goNamed(e.page);
|
onTap: () {
|
||||||
_closeDrawer();
|
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(),
|
.toList(),
|
||||||
).paddingSymmetric(vertical: 8),
|
),
|
||||||
const Divider(thickness: 0.3, height: 1),
|
const Divider(thickness: 0.3, height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AppNavigationRegions(
|
child: AppNavigationRegion(
|
||||||
|
isCollapsed: _isCollapsed,
|
||||||
onSelected: (item) {
|
onSelected: (item) {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
},
|
},
|
||||||
@ -179,18 +283,63 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
const Divider(thickness: 0.3, height: 1),
|
const Divider(thickness: 0.3, height: 1),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
if (_isCollapsed)
|
||||||
minTileHeight: 0,
|
Tooltip(
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
message: 'settings'.tr,
|
||||||
horizontal: 20,
|
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(
|
).paddingOnly(
|
||||||
top: 8,
|
top: 8,
|
||||||
|
@ -5,10 +5,15 @@ import 'package:solian/providers/content/channel.dart';
|
|||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
class AppNavigationRegions extends StatelessWidget {
|
class AppNavigationRegion extends StatelessWidget {
|
||||||
|
final bool isCollapsed;
|
||||||
final Function(Channel item) onSelected;
|
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) {
|
void _gotoChannel(Channel item) {
|
||||||
AppRouter.instance.pushReplacementNamed(
|
AppRouter.instance.pushReplacementNamed(
|
||||||
@ -25,6 +30,16 @@ class AppNavigationRegions extends StatelessWidget {
|
|||||||
Widget _buildEntry(BuildContext context, Channel item) {
|
Widget _buildEntry(BuildContext context, Channel item) {
|
||||||
const padding = EdgeInsets.symmetric(horizontal: 20);
|
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(
|
return ListTile(
|
||||||
minTileHeight: 0,
|
minTileHeight: 0,
|
||||||
leading: const Icon(Icons.tag_outlined),
|
leading: const Icon(Icons.tag_outlined),
|
||||||
@ -51,6 +66,27 @@ class AppNavigationRegions extends StatelessWidget {
|
|||||||
.where((x) => x.type == 0 && x.realmId != null)
|
.where((x) => x.type == 0 && x.realmId != null)
|
||||||
.toList();
|
.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(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverPadding(padding: EdgeInsets.only(top: 8)),
|
const SliverPadding(padding: EdgeInsets.only(top: 8)),
|
42
pubspec.lock
42
pubspec.lock
@ -865,10 +865,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe
|
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.3"
|
version: "14.2.7"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -953,10 +953,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56"
|
sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.12+12"
|
version: "0.8.12+13"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1153,10 +1153,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit
|
name: media_kit
|
||||||
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
|
sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.10+1"
|
version: "1.1.11"
|
||||||
media_kit_libs_android_video:
|
media_kit_libs_android_video:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1193,34 +1193,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_video
|
name: media_kit_libs_video
|
||||||
sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067"
|
sha256: "20bb4aefa8fece282b59580e1cd8528117297083a6640c98c2e98cfc96b93288"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.5"
|
||||||
media_kit_libs_windows_video:
|
media_kit_libs_windows_video:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_windows_video
|
name: media_kit_libs_windows_video
|
||||||
sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122"
|
sha256: "32654572167825c42c55466f5d08eee23ea11061c84aa91b09d0e0f69bdd0887"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.9"
|
version: "1.0.10"
|
||||||
media_kit_native_event_loop:
|
media_kit_native_event_loop:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_native_event_loop
|
name: media_kit_native_event_loop
|
||||||
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e
|
sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.9"
|
||||||
media_kit_video:
|
media_kit_video:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit_video
|
name: media_kit_video
|
||||||
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882
|
sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.4"
|
version: "1.2.5"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1385,10 +1385,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_html
|
name: permission_handler_html
|
||||||
sha256: d220eb8476b466d58b161e10b3001d93999010a26228a3fb89c4280db1249546
|
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3+1"
|
version: "0.1.3+2"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1481,10 +1481,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: process_run
|
name: process_run
|
||||||
sha256: c917dfb5f7afad4c7485bc00a4df038621248fce046105020cea276d1a87c820
|
sha256: "112a77da35be50617ed9e2230df68d0817972f225e7f97ce8336f76b4e601606"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
protobuf:
|
protobuf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1934,10 +1934,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
|
sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.9"
|
version: "6.3.10"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2140,4 +2140,4 @@ packages:
|
|||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.22.0"
|
flutter: ">=3.24.0"
|
||||||
|
@ -2,7 +2,7 @@ name: solian
|
|||||||
description: "The Solar Network App"
|
description: "The Solar Network App"
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 1.2.1+20
|
version: 1.2.1+23
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.4 <4.0.0"
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
|
Reference in New Issue
Block a user