diff --git a/android/app/src/main/kotlin/xyz/project/violet/MainActivity.kt b/android/app/src/main/kotlin/xyz/project/violet/MainActivity.kt index d2ecae8e4..a9348adba 100644 --- a/android/app/src/main/kotlin/xyz/project/violet/MainActivity.kt +++ b/android/app/src/main/kotlin/xyz/project/violet/MainActivity.kt @@ -9,6 +9,7 @@ import android.view.WindowManager import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugins.GeneratedPluginRegistrant @@ -17,6 +18,7 @@ class MainActivity : FlutterFragmentActivity() { private val VOLUME_CHANNEL = "xyz.project.violet/volume" private val NATIVELIBDIR_CHANNEL = "xyz.project.violet/nativelibdir" private val EXTERNAL_STORAGE_DIRECTORY_CHANNEL = "xyz.project.violet/externalStorageDirectory" + private val MISC_CHANNEL = "xyz.project.violet/misc" private val EXTERNAL_STORAGE_DIRECTORY_METHODS = mapOf( "getExternalStorageDirectory" to MethodCallHandler { call, result -> @@ -76,6 +78,13 @@ class MainActivity : FlutterFragmentActivity() { result.notImplemented() } } + + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, MISC_CHANNEL).setMethodCallHandler { call, result -> + when (call.method) { + "finishMainActivity" -> finishMainActivity(call, result) + else -> result.notImplemented() + } + } } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { @@ -86,4 +95,9 @@ class MainActivity : FlutterFragmentActivity() { return super.onKeyDown(keyCode, event) } + + private fun finishMainActivity(call: MethodCall, result: MethodChannel.Result) { + finish() + result.success(null) + } } diff --git a/assets/locale/en.json b/assets/locale/en.json index c706b7054..6480d776f 100644 --- a/assets/locale/en.json +++ b/assets/locale/en.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/assets/locale/eo.json b/assets/locale/eo.json index d8b64da18..c6e315c79 100644 --- a/assets/locale/eo.json +++ b/assets/locale/eo.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/assets/locale/it.json b/assets/locale/it.json index cdcaf6afb..eb271b286 100644 --- a/assets/locale/it.json +++ b/assets/locale/it.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/assets/locale/ja.json b/assets/locale/ja.json index f3009274c..cbd2907a4 100644 --- a/assets/locale/ja.json +++ b/assets/locale/ja.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/assets/locale/ko.json b/assets/locale/ko.json index 2158012d3..dfda64f38 100644 --- a/assets/locale/ko.json +++ b/assets/locale/ko.json @@ -305,5 +305,6 @@ "lab": "실험실", "realtimeuserrecord": "실시간 유저 레코드", "manualupdate": "수동 업데이트", - "cannotuseios": "iOS에선 사용할 수 없어요 :(" + "cannotuseios": "iOS에선 사용할 수 없어요 :(", + "exitTheApp": "앱 종료하기" } diff --git a/assets/locale/pt.json b/assets/locale/pt.json index 2a42c0073..61d8e90f6 100644 --- a/assets/locale/pt.json +++ b/assets/locale/pt.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/assets/locale/zh.json b/assets/locale/zh.json index fa6c0983f..3a6683edc 100644 --- a/assets/locale/zh.json +++ b/assets/locale/zh.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/assets/locale/zh_Hans.json b/assets/locale/zh_Hans.json index d8822bfd5..85029def9 100644 --- a/assets/locale/zh_Hans.json +++ b/assets/locale/zh_Hans.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/assets/locale/zh_Hant.json b/assets/locale/zh_Hant.json index 6a9123087..8d0c87000 100644 --- a/assets/locale/zh_Hant.json +++ b/assets/locale/zh_Hant.json @@ -305,5 +305,6 @@ "lab": "Laboratory", "realtimeuserrecord": "Real Time User Record", "manualupdate": "Manual Update", - "cannotuseios": "You cannot use update on iOS :(" + "cannotuseios": "You cannot use update on iOS :(", + "exitTheApp": "Exit the app" } diff --git a/lib/component/hitomi/population.dart b/lib/component/hitomi/population.dart index 3f0e386bc..1a9ae7348 100644 --- a/lib/component/hitomi/population.dart +++ b/lib/component/hitomi/population.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'package:flutter/services.dart'; import 'package:violet/database/query.dart'; @@ -15,18 +16,35 @@ class Population { String data; if (Platform.environment.containsKey('FLUTTER_TEST')) { - var file = File('/home/ubuntu/violet/assets/rank/population.json'); + final file = File('/home/ubuntu/violet/assets/rank/population.json'); data = await file.readAsString(); } else { data = await rootBundle.loadString('assets/rank/population.json'); } - List dataPopulation = json.decode(data); - population = {}; + Future> decodeJsonData() async { + final population = {}; - for (int i = 0; i < dataPopulation.length; i++) { - population[dataPopulation[i] as int] = i; + json.decode( + data, + reviver: (keyObject, valueObject) { + if (keyObject == null) { + return null; + } + + int key = keyObject as int; + int value = (valueObject as num).toInt(); + + population[value] = key; + + return null; + }, + ); + + return population; } + + population = await Isolate.run(decodeJsonData); } static void sortByPopulation(List qr) { diff --git a/lib/component/hitomi/related.dart b/lib/component/hitomi/related.dart index c4f51e590..1dd8d9041 100644 --- a/lib/component/hitomi/related.dart +++ b/lib/component/hitomi/related.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'package:flutter/services.dart'; @@ -13,20 +14,35 @@ class Related { String data; if (Platform.environment.containsKey('FLUTTER_TEST')) { - var file = File('/home/ubuntu/violet/assets/rank/related.json'); + final file = File('/home/ubuntu/violet/assets/rank/related.json'); data = await file.readAsString(); } else { data = await rootBundle.loadString('assets/rank/related.json'); } - Map dataMap = json.decode(data); - - related = >{}; + Future>> decodeJsonData() async { + final related = >{}; + + json.decode( + data, + reviver: (key, value) { + if (key == null) { + return value; + } + + if (key is String) { + related[int.parse(key)] = (value as List).cast(); + return null; + } else { + return value; + } + }, + ); + + return related; + } - dataMap.entries.forEach((element) { - related[int.parse(element.key)] = - (element.value as List).map((e) => e as int).toList(); - }); + related = await Isolate.run(decodeJsonData); } static bool existsRelated(int articleId) { diff --git a/lib/component/hitomi/tag_translate.dart b/lib/component/hitomi/tag_translate.dart index 2c50b4077..ddc9657b2 100644 --- a/lib/component/hitomi/tag_translate.dart +++ b/lib/component/hitomi/tag_translate.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'package:flutter/services.dart'; import 'package:tuple/tuple.dart'; @@ -21,25 +22,51 @@ class TagTranslate { String data; if (Platform.environment.containsKey('FLUTTER_TEST')) { - var file = File('/home/ubuntu/violet/assets/locale/tag/korean.json'); + final file = File('/home/ubuntu/violet/assets/locale/tag/korean.json'); data = await file.readAsString(); } else { data = await rootBundle .loadString('assets/locale/tag/$defaultLanguage.json'); } - Map result = json.decode(data); - - _translateMap = {}; - _reverseAndroMap = {}; - - result.entries.forEach((element) { - if (element.value.toString().trim() == '') return; - if (_translateMap.containsKey(element.key)) return; - _translateMap[element.key] = element.value as String; - _reverseAndroMap[disassembly((element.value as String) - .replaceAll('female:', '') - .replaceAll('male:', ''))] = element.key; - }); + + Future<(Map, Map)> decodeJsonData() async { + final translateMap = {}; + final reverseAndroMap = {}; + + json.decode( + data, + reviver: (keyObject, valueObject) { + if (keyObject == null) { + return null; + } + + final key = keyObject.toString(); + final value = valueObject.toString(); + + if (value.trim().isEmpty) { + return null; + } + + if (!translateMap.containsKey(key)) { + return null; + } + + translateMap[key] = value; + reverseAndroMap[disassembly(value) + .replaceAll('female:', '') + .replaceAll('male:', '')] = key; + + return null; + }, + ); + + return (translateMap, reverseAndroMap); + } + + final (translateMap, reverseAndroMap) = await Isolate.run(decodeJsonData); + + _translateMap = translateMap; + _reverseAndroMap = reverseAndroMap; } static String of(String classification, String key) { diff --git a/lib/pages/database_download/database_download_page.dart b/lib/pages/database_download/database_download_page.dart index abc618279..f9116dd87 100644 --- a/lib/pages/database_download/database_download_page.dart +++ b/lib/pages/database_download/database_download_page.dart @@ -4,10 +4,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:ui'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -18,6 +20,7 @@ import 'package:violet/database/query.dart'; import 'package:violet/locale/locale.dart'; import 'package:violet/log/log.dart'; import 'package:violet/pages/database_download/decompress.dart'; +import 'package:violet/platform/misc.dart'; import 'package:violet/settings/settings.dart'; import 'package:violet/variables.dart'; import 'package:violet/version/sync.dart'; @@ -35,16 +38,18 @@ class DataBaseDownloadPage extends StatefulWidget { }) : super(key: key); @override - State createState() => DataBaseDownloadPagepState(); + State createState() => DataBaseDownloadPageState(); } -class DataBaseDownloadPagepState extends State { +class DataBaseDownloadPageState extends State { bool downloading = false; var baseString = ''; var progressString = ''; var downString = ''; var speedString = ''; + var _showCloseAppButton = false; + @override void initState() { super.initState(); @@ -95,7 +100,8 @@ class DataBaseDownloadPagepState extends State { Timer timer = Timer.periodic( const Duration(seconds: 1), (Timer timer) => setState(() { - speedString = '${tlatest / 1024} KB/S'; + final speed = tlatest / 1024; + speedString = '${_formatNumberWithComma(speed)} KB/s'; tlatest = tnu; tnu = 0; })); @@ -113,8 +119,10 @@ class DataBaseDownloadPagepState extends State { setState( () { downloading = true; - progressString = '${((rec / total) * 100).toStringAsFixed(0)}%'; - downString = '[${numberWithComma(rec)}/${numberWithComma(total)}]'; + final progressPercent = (rec / total) * 100; + progressString = '${_formatNumberWithComma(progressPercent)}%'; + downString = + '[${_formatNumberWithComma(rec)}/${_formatNumberWithComma(total)}]'; }, ); }); @@ -163,6 +171,7 @@ class DataBaseDownloadPagepState extends State { } else { setState(() { baseString = Translations.instance!.trans('dbdcomplete'); + _showCloseAppButton = true; }); } @@ -194,7 +203,8 @@ class DataBaseDownloadPagepState extends State { Timer timer = Timer.periodic( const Duration(seconds: 1), (Timer timer) => setState(() { - speedString = '${tlatest / 1024} KB/S'; + final speed = tlatest / 1024; + speedString = '${_formatNumberWithComma(speed)} KB/s'; tlatest = tnu; tnu = 0; })); @@ -212,8 +222,10 @@ class DataBaseDownloadPagepState extends State { setState( () { downloading = true; - progressString = '${((rec / total) * 100).toStringAsFixed(0)}%'; - downString = '[${numberWithComma(rec)}/${numberWithComma(total)}]'; + final progressPercent = (rec / total) * 100; + progressString = '${_formatNumberWithComma(progressPercent)}%'; + downString = + '[${_formatNumberWithComma(rec)}/${_formatNumberWithComma(total)}]'; }, ); }); @@ -239,6 +251,7 @@ class DataBaseDownloadPagepState extends State { } else { setState(() { baseString = Translations.instance!.trans('dbdcomplete'); + _showCloseAppButton = true; }); } @@ -486,6 +499,7 @@ class DataBaseDownloadPagepState extends State { setState(() { baseString = Translations.instance!.trans('dbdcomplete'); + _showCloseAppButton = true; }); break; } @@ -493,8 +507,20 @@ class DataBaseDownloadPagepState extends State { } } - String numberWithComma(int param) { - return NumberFormat('###,###,###,###').format(param).replaceAll(' ', ''); + static final _commaFormatter = NumberFormat('#,###.#'); + + String _formatNumberWithComma(num param) { + return _commaFormatter.format(param).replaceAll(' ', ''); + } + + Future _exitApplication() async { + if (Platform.isAndroid) { + await PlatformMiscMethods.instance.finishMainActivity(); + } else if (Platform.isIOS) { + exit(0); + } else { + ServicesBinding.instance.exitApplication(AppExitType.required); + } } @override @@ -543,7 +569,19 @@ class DataBaseDownloadPagepState extends State { ), ), ) - : Text(baseString), + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(baseString), + if (_showCloseAppButton) ...[ + const SizedBox(height: 32.0), + ElevatedButton( + onPressed: _exitApplication, + child: Text(Translations.of(context).trans('exitTheApp')), + ), + ], + ], + ), ), ); } diff --git a/lib/platform/misc.dart b/lib/platform/misc.dart new file mode 100644 index 000000000..886c33a7c --- /dev/null +++ b/lib/platform/misc.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:flutter/services.dart'; + +class PlatformMiscMethods { + const PlatformMiscMethods._(); + + static const instance = PlatformMiscMethods._(); + + final _methodChannel = const MethodChannel('xyz.project.violet/misc'); + + Future finishMainActivity() async { + if (!Platform.isAndroid) { + throw UnsupportedError('Android only'); + } + + await _methodChannel.invokeMethod('finishMainActivity'); + } +}