Compare commits

..

6 Commits

Author SHA1 Message Date
f3375070a0 🚀 Launch 1.2.1+37 2024-09-15 17:46:48 +08:00
204df3306e 🐛 Fix notification services 2024-09-15 17:19:55 +08:00
aeaade9590 🐛 Fix unauthorized things 2024-09-15 16:54:07 +08:00
306ce9e2b4 Optimize notification background service 2024-09-15 16:02:56 +08:00
a487924300 Android background notification service 2024-09-15 15:55:14 +08:00
ad66c11593 ♻️ Implement delete (recreate) local database 2024-09-15 12:25:50 +08:00
45 changed files with 457 additions and 112 deletions

View File

@@ -3,6 +3,7 @@
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -84,6 +85,11 @@
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<service
android:name="id.flutter.flutter_background_service.BackgroundService"
android:foregroundServiceType="remoteMessaging"
/>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -54,6 +54,8 @@
"edit": "Edit",
"delete": "Delete",
"settings": "Settings",
"settingsNotificationBgService": "Background Notification Service",
"settingsNotificationBgServiceDesc": "A notification service is always installed on the device, so that some devices that do not support push notifications can receive notifications in the background. When this feature is enabled, push notifications will not be registered with the server, and you will always appear to be online in the eyes of others (except for invisible). You may need to turn off power and traffic optimization in the settings.",
"search": "Search",
"post": "Post",
"article": "Article",
@@ -373,7 +375,8 @@
"callStatusReconnected": "Reconnecting",
"messageOutOfSync": "May Out of Sync with Server",
"messageOutOfSyncCaption": "Since the App has entered the background, there may be a time difference between the message list and the server. Click to Refresh.",
"messageHistoryWipe": "Wipe local message history",
"localDatabaseWipe": "Wipe local database",
"localDatabaseSize": "Overall database size: @size",
"unknown": "Unknown",
"collapse": "Collapse",
"expand": "Expand",
@@ -392,5 +395,7 @@
"userLevel11": "Legend",
"userLevel12": "Mythic",
"userLevel13": "Immortal",
"postBrowsingIn": "Browsing in @region"
"postBrowsingIn": "Browsing in @region",
"needRestartToApply": "Restart the application to take effect",
"holdToSeeDetail": "Long press / Mouse hover to see detail"
}

View File

@@ -14,6 +14,8 @@
"edit": "编辑",
"delete": "删除",
"settings": "设置",
"settingsNotificationBgService": "常驻通知服务",
"settingsNotificationBgServiceDesc": "在设备常驻一个通知服务,使得部分不支持推送通知的设备可以在后台收到通知;启用该功能的情况下不会向服务器注册推送通知,并且你会始终在他人眼中成为在线(隐身除外);可能需要在设置中关闭电量与流量优化。",
"page": "页面",
"draft": "草稿",
"draftSave": "存为草稿",
@@ -374,7 +376,8 @@
"callStatusReconnected": "重连中",
"messageOutOfSync": "消息可能与服务器脱节",
"messageOutOfSyncCaption": "由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。",
"messageHistoryWipe": "清除消息记录",
"localDatabaseWipe": "清除本地数据库",
"localDatabaseSize": "本地数据库大小:@size",
"unknown": "未知",
"collapse": "折叠",
"expand": "展开",
@@ -393,5 +396,7 @@
"userLevel11": "名垂千古",
"userLevel12": "独占鳌头",
"userLevel13": "万古流芳",
"postBrowsingIn": "浏览 @region 内的帖子中"
"postBrowsingIn": "浏览 @region 内的帖子中",
"needRestartToApply": "需要重启应用来生效",
"holdToSeeDetail": "长按 / 鼠标悬浮来查看详情"
}

View File

@@ -154,8 +154,12 @@ PODS:
- PromisesSwift (~> 2.1)
- FirebaseSharedSwift (11.1.0)
- Flutter (1.0.0)
- flutter_background_service_ios (0.0.3):
- Flutter
- flutter_keyboard_visibility (0.0.1):
- Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_native_splash (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
@@ -302,7 +306,9 @@ DEPENDENCIES:
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- firebase_performance (from `.symlinks/plugins/firebase_performance/ios`)
- Flutter (from `Flutter`)
- flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
@@ -377,8 +383,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/firebase_performance/ios"
Flutter:
:path: Flutter
flutter_background_service_ios:
:path: ".symlinks/plugins/flutter_background_service_ios/ios"
flutter_keyboard_visibility:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_secure_storage:
@@ -454,7 +464,9 @@ SPEC CHECKSUMS:
FirebaseSessions: 78f137e68dc01ca71606169ba4ac73b98c13752a
FirebaseSharedSwift: 260a35e08943ec810d820a70bc0359136351d0c5
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_webrtc: 75b868e4f9e817c7a9a42ca4b6169063de4eec9f

109
lib/background.dart Normal file
View File

@@ -0,0 +1,109 @@
import 'dart:ui';
import 'package:flutter/material.dart' hide Notification;
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/websocket.dart';
FlutterBackgroundService? bgNotificationService;
void autoConfigureBackgroundNotificationService() async {
if (bgNotificationService != null) return;
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('service_background_notification') != true) return;
bgNotificationService = FlutterBackgroundService();
await bgNotificationService!.configure(
androidConfiguration: AndroidConfiguration(
onStart: onBackgroundNotificationServiceStart,
autoStart: true,
autoStartOnBoot: true,
isForegroundMode: false,
),
// This feature won't be able to use on iOS
// We got APNs support covered
iosConfiguration: IosConfiguration(
autoStart: false,
),
);
}
void autoStartBackgroundNotificationService() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('service_background_notification') != true) return;
if (bgNotificationService == null) return;
bgNotificationService!.startService();
}
void autoStopBackgroundNotificationService() async {
if (bgNotificationService == null) return;
if (await bgNotificationService!.isRunning()) {
bgNotificationService?.invoke('stopService');
}
}
@pragma('vm:entry-point')
void onBackgroundNotificationServiceStart(ServiceInstance service) async {
WidgetsFlutterBinding.ensureInitialized();
DartPluginRegistrant.ensureInitialized();
Get.put(AuthProvider());
Get.put(WebSocketProvider());
service.on('stopService').listen((event) {
service.stopSelf();
});
final auth = Get.find<AuthProvider>();
await auth.refreshAuthorizeStatus();
await auth.ensureCredentials();
if (!auth.isAuthorized.value) {
debugPrint(
'Background notification do nothing due to user didn\'t sign in.',
);
return;
}
const notificationChannelId = 'solian_notification_service';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
final ws = Get.find<WebSocketProvider>();
await ws.connect();
debugPrint('Background notification has been started');
ws.stream.stream.listen(
(event) {
debugPrint(
'Background notification service incoming message: ${event.method} ${event.message}',
);
if (event.method == 'notifications.new' && event.payload != null) {
final data = Notification.fromJson(event.payload!);
debugPrint(
'Background notification service got a notification id=${data.id}',
);
flutterLocalNotificationsPlugin.show(
data.id,
data.title,
[data.subtitle, data.body].where((x) => x != null).join('\n'),
const NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId,
'Solian Notification Service',
channelDescription: 'Notifications that sent via Solar Network',
importance: Importance.high,
icon: 'mipmap/ic_launcher',
),
),
);
}
},
);
}

View File

@@ -1,9 +1,11 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
extension SolianExtenions on BuildContext {
extension AppExtensions on BuildContext {
void showSnackbar(String content, {SnackBarAction? action}) {
ScaffoldMessenger.of(this).showSnackBar(SnackBar(
content: Text(content),
@@ -102,3 +104,24 @@ extension SolianExtenions on BuildContext {
);
}
}
extension ByteFormatter on int {
String formatBytes({int decimals = 2}) {
if (this == 0) return '0 Bytes';
const k = 1024;
final dm = decimals < 0 ? 0 : decimals;
final sizes = [
'Bytes',
'KiB',
'MiB',
'GiB',
'TiB',
'PiB',
'EiB',
'ZiB',
'YiB'
];
final i = (math.log(this) / math.log(k)).floor().toInt();
return '${(this / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
}
}

View File

@@ -2,12 +2,13 @@ import 'dart:ui';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide Notification;
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:provider/provider.dart';
import 'package:solian/background.dart';
import 'package:solian/bootstrapper.dart';
import 'package:solian/firebase_options.dart';
import 'package:solian/platform.dart';
@@ -41,6 +42,7 @@ void main() async {
await Future.wait([
_initializeFirebase(),
_initializePlatformComponents(),
_initializeBackgroundNotificationService(),
]);
GoRouter.optionURLReflectsImperativeAPIs = true;
@@ -64,6 +66,11 @@ Future<void> _initializeFirebase() async {
};
}
Future<void> _initializeBackgroundNotificationService() async {
autoConfigureBackgroundNotificationService();
autoStartBackgroundNotificationService();
}
Future<void> _initializePlatformComponents() async {
if (!PlatformInfo.isWeb) {
await protocolHandler.register('solink');
@@ -144,5 +151,7 @@ class SolianApp extends StatelessWidget {
Get.lazyPut(() => LinkExpandProvider());
Get.lazyPut(() => DailySignProvider());
Get.lazyPut(() => LastReadProvider());
Get.find<WebSocketProvider>().requestPermissions();
}
}

View File

@@ -6,8 +6,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart';
import 'package:get/get_connect/http/src/request/request.dart';
import 'package:solian/background.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/providers/database/database.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart';
@@ -198,6 +200,9 @@ class AuthProvider extends GetConnect {
Get.find<WebSocketProvider>().notifications.clear();
Get.find<WebSocketProvider>().notificationUnread.value = 0;
AppDatabase.removeDatabase();
autoStopBackgroundNotificationService();
storage.deleteAll();
}
@@ -211,6 +216,7 @@ class AuthProvider extends GetConnect {
}
Future<void> refreshUserProfile() async {
if (!isAuthorized.value) return;
final client = configureClient('auth');
final resp = await client.get('/users/me');
if (resp.statusCode != 200) {

View File

@@ -1,6 +1,11 @@
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:get/get.dart' hide Value;
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/database/tables/messages.dart';
import 'package:solian/models/event.dart';
@@ -17,8 +22,24 @@ class AppDatabase extends _$AppDatabase {
static QueryExecutor _openConnection() {
return driftDatabase(name: 'solar_network_local_db');
}
static Future<int> getDatabaseSize() async {
if (PlatformInfo.isWeb) return 0;
final basepath = await getApplicationDocumentsDirectory();
return await File(join(basepath.path, 'solar_network_local_db.sqlite'))
.length();
}
static Future<void> removeDatabase() async {
if (PlatformInfo.isWeb) return;
final basepath = await getApplicationDocumentsDirectory();
final file = File(join(basepath.path, 'solar_network_local_db.sqlite'));
await Get.find<DatabaseProvider>().database.close();
await file.delete();
Get.find<DatabaseProvider>().database = AppDatabase();
}
}
class DatabaseProvider extends GetxController {
final database = AppDatabase();
var database = AppDatabase();
}

View File

@@ -5,7 +5,9 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/models/packet.dart';
@@ -29,20 +31,46 @@ class WebSocketProvider extends GetxController {
@override
onInit() {
FirebaseMessaging.instance
.requestPermission(
alert: true,
announcement: true,
carPlay: true,
badge: true,
sound: true)
.then((status) {
notifyPrefetch();
});
notifyPrefetch();
super.onInit();
}
void requestPermissions() {
try {
FirebaseMessaging.instance.requestPermission(
alert: true,
announcement: true,
carPlay: true,
badge: true,
sound: true);
} catch (_) {
// When firebase isn't initialized (background service)
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission();
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
MacOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
}
Future<void> connect({noRetry = false}) async {
if (isConnected.value) {
return;
@@ -90,6 +118,10 @@ class WebSocketProvider extends GetxController {
final packet = NetworkPackage.fromJson(jsonDecode(event));
log('Websocket incoming message: ${packet.method} ${packet.message}');
stream.sink.add(packet);
if (packet.method == 'notifications.new') {
notifications.add(Notification.fromJson(packet.payload!));
notificationUnread.value++;
}
},
onDone: () {
isConnected.value = false;
@@ -120,6 +152,12 @@ class WebSocketProvider extends GetxController {
}
Future<void> registerPushNotifications() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('service_background_notification') == true) {
log('Background notification service has been enabled, skip register push notifications');
return;
}
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return;

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:solian/background.dart';
import 'package:solian/exts.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/providers/auth.dart';
@@ -22,7 +23,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
void requestResetPassword() async {
void _requestResetPassword() async {
final username = _usernameController.value.text;
if (username.isEmpty) {
context.showErrorDialog('signinResetPasswordHint'.tr);
@@ -52,7 +53,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
context.showModalDialog('done'.tr, 'signinResetPasswordSent'.tr);
}
void performAction() async {
void _performAction() async {
final AuthProvider auth = Get.find();
final username = _usernameController.value.text;
@@ -100,6 +101,8 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
}
Get.find<WebSocketProvider>().registerPushNotifications();
autoConfigureBackgroundNotificationService();
autoStartBackgroundNotificationService();
Navigator.pop(context, true);
}
@@ -121,7 +124,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
final uri = url.replaceFirst('solink://', '');
if (uri == 'auth?status=done') {
closeInAppWebView();
performAction();
_performAction();
}
}
@@ -175,19 +178,19 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => performAction(),
onSubmitted: (_) => _performAction(),
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: _isBusy ? null : () => requestResetPassword(),
onPressed: _isBusy ? null : () => _requestResetPassword(),
style: TextButton.styleFrom(foregroundColor: Colors.grey),
child: Text('forgotPassword'.tr),
),
TextButton(
onPressed: _isBusy ? null : () => performAction(),
onPressed: _isBusy ? null : () => _performAction(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -3,6 +3,8 @@ import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/exts.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/database/database.dart';
import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/router.dart';
import 'package:solian/theme.dart';
@@ -15,7 +17,7 @@ class SettingScreen extends StatefulWidget {
}
class _SettingScreenState extends State<SettingScreen> {
late final SharedPreferences _prefs;
SharedPreferences? _prefs;
Widget _buildCaptionHeader(String title) {
return Container(
@@ -41,7 +43,7 @@ class _SettingScreenState extends State<SettingScreen> {
seedColor: color,
),
);
_prefs.setInt('global_theme_color', color.value);
_prefs?.setInt('global_theme_color', color.value);
context.clearSnackbar();
context.showSnackbar('themeColorApplied'.tr);
},
@@ -61,6 +63,9 @@ class _SettingScreenState extends State<SettingScreen> {
super.initState();
SharedPreferences.getInstance().then((inst) {
_prefs = inst;
if (mounted) {
setState(() {});
}
});
}
@@ -80,7 +85,60 @@ class _SettingScreenState extends State<SettingScreen> {
.toList(),
).paddingSymmetric(horizontal: 12, vertical: 8),
),
_buildCaptionHeader('notification'.tr),
Tooltip(
message: 'settingsNotificationBgServiceDesc'.tr,
child: CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
secondary: const Icon(Icons.system_security_update_warning),
enabled: PlatformInfo.isAndroid,
title: Text('settingsNotificationBgService'.tr),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('holdToSeeDetail'.tr),
Text(
'needRestartToApply'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
)
],
),
value:
_prefs?.getBool('service_background_notification') ?? false,
onChanged: (value) {
_prefs
?.setBool('service_background_notification', value ?? false)
.then((_) {
setState(() {});
});
},
),
),
_buildCaptionHeader('more'.tr),
ListTile(
leading: const Icon(Icons.delete_sweep),
trailing: const Icon(Icons.chevron_right),
subtitle: FutureBuilder(
future: AppDatabase.getDatabaseSize(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('localDatabaseSize'.trParams(
{'size': 'unknown'.tr},
));
}
return Text('localDatabaseSize'.trParams(
{'size': snapshot.data!.formatBytes()},
));
},
),
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
title: Text('localDatabaseWipe'.tr),
onTap: () {
AppDatabase.removeDatabase().then((_) {
setState(() {});
});
},
),
ListTile(
leading: const Icon(Icons.info_outline),
trailing: const Icon(Icons.chevron_right),
@@ -90,15 +148,6 @@ class _SettingScreenState extends State<SettingScreen> {
AppRouter.instance.pushNamed('about');
},
),
ListTile(
leading: const Icon(Icons.delete_sweep),
trailing: const Icon(Icons.chevron_right),
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
title: Text('messageHistoryWipe'.tr),
onTap: () {
// TODO Wipe message history
},
),
],
),
);

View File

@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
import 'package:desktop_drop/desktop_drop.dart';
import 'package:dismissible_page/dismissible_page.dart';
@@ -209,25 +208,6 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
});
}
String _formatBytes(int bytes, {int decimals = 2}) {
if (bytes == 0) return '0 Bytes';
const k = 1024;
final dm = decimals < 0 ? 0 : decimals;
final sizes = [
'Bytes',
'KiB',
'MiB',
'GiB',
'TiB',
'PiB',
'EiB',
'ZiB',
'YiB'
];
final i = (math.log(bytes) / math.log(k)).floor().toInt();
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
}
void _revertMetadataList() {
final AttachmentProvider attach = Get.find();
@@ -367,7 +347,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
return const SizedBox.shrink();
}
return Text(
_formatBytes(snapshot.data!),
snapshot.data!.formatBytes(),
style: Theme.of(context).textTheme.bodySmall,
);
},
@@ -502,7 +482,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
),
Text(
'${fileType[0].toUpperCase()}${fileType.substring(1)} · ${_formatBytes(element.size)}',
'${fileType[0].toUpperCase()}${fileType.substring(1)} · ${element.size.formatBytes()}',
style: const TextStyle(fontSize: 12),
),
],

View File

@@ -38,25 +38,6 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
String _formatBytes(int bytes, {int decimals = 2}) {
if (bytes == 0) return '0 Bytes';
const k = 1024;
final dm = decimals < 0 ? 0 : decimals;
final sizes = [
'Bytes',
'KiB',
'MiB',
'GiB',
'TiB',
'PiB',
'EiB',
'ZiB',
'YiB'
];
final i = (math.log(bytes) / math.log(k)).floor().toInt();
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
}
double _getRatio() {
final value = widget.item.metadata?['ratio'];
if (value == null) return 1;
@@ -274,7 +255,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
style: metaTextStyle,
),
Text(
_formatBytes(widget.item.size),
widget.item.size.formatBytes(),
style: metaTextStyle,
),
Text(

View File

@@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/providers/durations.dart';
import 'package:solian/services.dart';
@@ -377,25 +378,6 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
);
}
String _formatBytes(int bytes, {int decimals = 2}) {
if (bytes == 0) return '0 Bytes';
const k = 1024;
final dm = decimals < 0 ? 0 : decimals;
final sizes = [
'Bytes',
'KiB',
'MiB',
'GiB',
'TiB',
'PiB',
'EiB',
'ZiB',
'YiB'
];
final i = (math.log(bytes) / math.log(k)).floor().toInt();
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
}
@override
void initState() {
super.initState();
@@ -471,7 +453,7 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
),
),
Text(
_formatBytes(widget.item.size),
widget.item.size.formatBytes(),
style: GoogleFonts.robotoMono(
fontSize: 12,
shadows: labelShadows,

View File

@@ -204,7 +204,8 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
@override
void initState() {
super.initState();
_getStatus();
final AuthProvider auth = Get.find();
if (auth.isAuthorized.value) _getStatus();
Future.delayed(Duration.zero, () => _autoResize());
_drawerAnimationController.addListener(() {
if (_drawerAnimation.value > 180 && _isCollapsed) {

View File

@@ -13,6 +13,7 @@ import firebase_analytics
import firebase_core
import firebase_crashlytics
import firebase_messaging
import flutter_local_notifications
import flutter_secure_storage_macos
import flutter_webrtc
import gal
@@ -41,6 +42,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))

View File

@@ -21,19 +21,19 @@ PODS:
- Firebase/Messaging (11.0.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.0.0)
- firebase_analytics (11.3.0):
- firebase_analytics (11.3.1):
- Firebase/Analytics (= 11.0.0)
- firebase_core
- FlutterMacOS
- firebase_core (3.4.0):
- firebase_core (3.4.1):
- Firebase/CoreOnly (~> 11.0.0)
- FlutterMacOS
- firebase_crashlytics (4.1.0):
- firebase_crashlytics (4.1.1):
- Firebase/CoreOnly (~> 11.0.0)
- Firebase/Crashlytics (~> 11.0.0)
- firebase_core
- FlutterMacOS
- firebase_messaging (15.1.0):
- firebase_messaging (15.1.1):
- Firebase/CoreOnly (~> 11.0.0)
- Firebase/Messaging (~> 11.0.0)
- firebase_core
@@ -97,6 +97,8 @@ PODS:
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- PromisesSwift (~> 2.1)
- flutter_local_notifications (0.0.1):
- FlutterMacOS
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
- flutter_webrtc (0.11.3):
@@ -156,7 +158,7 @@ PODS:
- GoogleUtilities/UserDefaults (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- livekit_client (2.2.4):
- livekit_client (2.2.5):
- FlutterMacOS
- WebRTC-SDK (= 125.6422.04)
- macos_window_utils (1.0.0):
@@ -194,6 +196,24 @@ PODS:
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
- "sqlite3 (3.46.1+1)":
- "sqlite3/common (= 3.46.1+1)"
- "sqlite3/common (3.46.1+1)"
- "sqlite3/dbstatvtab (3.46.1+1)":
- sqlite3/common
- "sqlite3/fts5 (3.46.1+1)":
- sqlite3/common
- "sqlite3/perf-threadsafe (3.46.1+1)":
- sqlite3/common
- "sqlite3/rtree (3.46.1+1)":
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- FlutterMacOS
- "sqlite3 (~> 3.46.0+1)"
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
- url_launcher_macos (0.0.1):
- FlutterMacOS
- wakelock_plus (0.0.1):
@@ -209,6 +229,7 @@ DEPENDENCIES:
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
- firebase_crashlytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos`)
- firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`)
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
@@ -226,6 +247,7 @@ DEPENDENCIES:
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
@@ -247,6 +269,7 @@ SPEC REPOS:
- nanopb
- PromisesObjC
- PromisesSwift
- sqlite3
- WebRTC-SDK
EXTERNAL SOURCES:
@@ -266,6 +289,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos
firebase_messaging:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos
flutter_local_notifications:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
flutter_secure_storage_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
flutter_webrtc:
@@ -300,6 +325,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
sqlite3_flutter_libs:
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_plus:
@@ -311,10 +338,10 @@ SPEC CHECKSUMS:
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9
firebase_analytics: e9ae2af44afdaecf076797dcf9f3cd9c71ad8ac6
firebase_core: ca9bcfb9835e1bcd5ff205e24a541f6bc04e7a35
firebase_crashlytics: 87ec8afd46aeceddb565f7469df437c83fb0e01f
firebase_messaging: c834894f659ce965ba4f93fe5d0dc5d705a3cc88
firebase_analytics: 2169e28bb3ee1f765efe0fd4f5b5f625d92fda13
firebase_core: 3f80bec72646b26618f0497e74ce8bcd608f03ca
firebase_crashlytics: 6b1e511297406a6efd4405dc6301da8843b9dfe1
firebase_messaging: ce70e6615f0cd906d80b7a651b960d76dad6de56
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
FirebaseCoreExtension: aa5c9779c2d0d39d83f1ceb3fdbafe80c4feecfa
@@ -324,6 +351,7 @@ SPEC CHECKSUMS:
FirebaseMessaging: d2d1d9c62c46dd2db49a952f7deb5b16ad2c9742
FirebaseRemoteConfigInterop: abf8b1bbc0bf1b84abd22b66746926410bf91a87
FirebaseSessions: 78f137e68dc01ca71606169ba4ac73b98c13752a
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
flutter_webrtc: 2b4e4a2de70a1485836e40fd71a7a94c77d49bd9
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
@@ -331,7 +359,7 @@ SPEC CHECKSUMS:
GoogleAppMeasurement: 6e49ffac7d3f2c3ded9cc663f912a13b67bbd0de
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
livekit_client: 95f3b71e6545845aa658a6df0a3a62dcc3471d7c
livekit_client: be04a950a4b84b9dbc87507ffad5154fe75fa067
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
@@ -347,6 +375,8 @@ SPEC CHECKSUMS:
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3

View File

@@ -635,6 +635,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.0"
flutter_background_service:
dependency: "direct main"
description:
name: flutter_background_service
sha256: d32f078ec57647c9cfd6e1a8da9297f7d8f021d4dcc204a35aaad2cdbfe255f0
url: "https://pub.dev"
source: hosted
version: "5.0.10"
flutter_background_service_android:
dependency: transitive
description:
name: flutter_background_service_android
sha256: "39da42dddf877beeef82bc2583130d8bedb4d0765e99ca9e7b4a32e8c6abd239"
url: "https://pub.dev"
source: hosted
version: "6.2.7"
flutter_background_service_ios:
dependency: transitive
description:
name: flutter_background_service_ios
sha256: "6037ffd45c4d019dab0975c7feb1d31012dd697e25edc05505a4a9b0c7dc9fba"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
flutter_background_service_platform_interface:
dependency: transitive
description:
name: flutter_background_service_platform_interface
sha256: ca74aa95789a8304f4d3f57f07ba404faa86bed6e415f83e8edea6ad8b904a41
url: "https://pub.dev"
source: hosted
version: "5.1.2"
flutter_cache_manager:
dependency: "direct main"
description:
@@ -715,6 +747,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: c500d5d9e7e553f06b61877ca6b9c8b92c570a4c8db371038702e8ce57f8a50f
url: "https://pub.dev"
source: hosted
version: "17.2.2"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af
url: "https://pub.dev"
source: hosted
version: "4.0.1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66"
url: "https://pub.dev"
source: hosted
version: "7.2.0"
flutter_markdown:
dependency: "direct main"
description:
@@ -1366,7 +1422,7 @@ packages:
source: hosted
version: "1.0.1"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
@@ -1930,6 +1986,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.7.0"
timezone:
dependency: transitive
description:
name: timezone
sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
timing:
dependency: transitive
description:

View File

@@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App"
publish_to: "none"
version: 1.2.1+36
version: 1.2.1+37
environment:
sdk: ">=3.3.4 <4.0.0"
@@ -78,6 +78,9 @@ dependencies:
drift: ^2.20.2
drift_flutter: ^0.2.0
very_good_infinite_list: ^0.8.0
path_provider: ^2.1.4
flutter_background_service: ^5.0.10
flutter_local_notifications: ^17.2.2
dev_dependencies:
flutter_test:
@@ -124,9 +127,7 @@ flutter:
weight: 700
flutter_launcher_icons:
android:
generate: "launcher_icon"
image_path: "assets/icon.png"
android: false
ios: true
image_path: "assets/icon.png"
min_sdk_android: 21

View File

@@ -35,6 +35,8 @@
<link rel="manifest" href="manifest.json">
<style id="splash-screen-style">
html {
height: 100%
@@ -106,6 +108,7 @@
document.body.style.background = "transparent";
}
</script>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
</head>
<body>
@@ -115,6 +118,7 @@
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
</picture>
<script src="flutter_bootstrap.js" async=""></script>