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 a9348adba..a08a189af 100644 --- a/android/app/src/main/kotlin/xyz/project/violet/MainActivity.kt +++ b/android/app/src/main/kotlin/xyz/project/violet/MainActivity.kt @@ -1,5 +1,7 @@ package xyz.project.violet +import android.app.Activity +import android.content.Intent import android.os.Build import android.os.Bundle import android.os.Environment @@ -13,6 +15,8 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugins.GeneratedPluginRegistrant +import java.io.FileInputStream +import java.io.FileOutputStream class MainActivity : FlutterFragmentActivity() { private val VOLUME_CHANNEL = "xyz.project.violet/volume" @@ -82,6 +86,7 @@ class MainActivity : FlutterFragmentActivity() { MethodChannel(flutterEngine.dartExecutor.binaryMessenger, MISC_CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "finishMainActivity" -> finishMainActivity(call, result) + "exportFile" -> exportFile(call, result) else -> result.notImplemented() } } @@ -100,4 +105,80 @@ class MainActivity : FlutterFragmentActivity() { finish() result.success(null) } + + private class ExportFileRequest( + val filePath: String, + val call: MethodCall, + val result: MethodChannel.Result, + ) + + private var nextExportFileRequestCode = 1000001; + private val exportFileRequestMap = hashMapOf() + + private fun exportFile(call: MethodCall, result: MethodChannel.Result) { + val filePath = call.argument("filePath") + val mimeType = call.argument("mimeType") + val fileNameToSaveAs = call.argument("fileNameToSaveAs") + + if (filePath == null) { + result.error("noArgument", "filePath", null) + return + } + + if (mimeType == null) { + result.error("noArgument", "mimeType", null) + return + } + + if (fileNameToSaveAs == null) { + result.error("noArgument", "fileNameToSaveAs", null) + return + } + + try { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = mimeType + putExtra(Intent.EXTRA_TITLE, fileNameToSaveAs) + } + + exportFileRequestMap[nextExportFileRequestCode] = + ExportFileRequest(filePath, call, result) + startActivityForResult(intent, nextExportFileRequestCode++); + } catch (e: Throwable) { + result.error("exception", e.toString(), e); + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + val request = exportFileRequestMap[requestCode] ?: return + + try { + if (resultCode != Activity.RESULT_OK) { + request.result.error("intentResultFail", "resultCode=$resultCode", null) + return + } + + try { + val uri = data!!.data!! + val targetFileDescriptor = contentResolver.openFileDescriptor(uri, "w") + + val input = FileInputStream(request.filePath) + val output = FileOutputStream(targetFileDescriptor!!.fileDescriptor) + + input.copyTo(output) + + input.close() + output.close() + + request.result.success(null) + } catch (e: Throwable) { + request.result.error("exception", e.toString(), e); + } + } finally { + exportFileRequestMap.remove(requestCode) + } + } } diff --git a/lib/pages/settings/settings_page.dart b/lib/pages/settings/settings_page.dart index 4472b5c04..6128bbe05 100644 --- a/lib/pages/settings/settings_page.dart +++ b/lib/pages/settings/settings_page.dart @@ -60,6 +60,7 @@ import 'package:violet/pages/settings/tag_rebuild_page.dart'; import 'package:violet/pages/settings/tag_selector.dart'; import 'package:violet/pages/settings/version_page.dart'; import 'package:violet/pages/splash/splash_page.dart'; +import 'package:violet/platform/misc.dart'; import 'package:violet/server/violet.dart'; import 'package:violet/settings/settings.dart'; import 'package:violet/style/palette.dart'; @@ -1994,36 +1995,45 @@ class _SettingsPageState extends State title: Text(Translations.of(context).trans('exportingbookmark')), trailing: const Icon(Icons.keyboard_arrow_right), onTap: () async { - if (!await Permission.storage.isGranted) { - if (await Permission.storage.request() == - PermissionStatus.denied) { - flutterToast.showToast( - child: ToastWrapper( - isCheck: false, - msg: Translations.of(context).trans('noauth'), - ), - gravity: ToastGravity.BOTTOM, - toastDuration: const Duration(seconds: 4), - ); + final dir = Platform.isIOS + ? await getApplicationSupportDirectory() + : (await getApplicationDocumentsDirectory()); + final bookmarkDatabaseFile = File('${dir.path}/user.db'); - return; - } - } + if (Platform.isAndroid) { + await PlatformMiscMethods.instance.exportFile( + bookmarkDatabaseFile.path, + mimeType: 'application/vnd.sqlite3', + fileNameToSaveAs: 'violet-bookmarks.db', + ); + } else { + if (!await Permission.storage.isGranted) { + if (await Permission.storage.request() == + PermissionStatus.denied) { + flutterToast.showToast( + child: ToastWrapper( + isCheck: false, + msg: Translations.of(context).trans('noauth'), + ), + gravity: ToastGravity.BOTTOM, + toastDuration: const Duration(seconds: 4), + ); - final selectedPath = await FilePicker.platform.getDirectoryPath(); + return; + } + } - if (selectedPath == null) { - return; - } + final selectedPath = + await FilePicker.platform.getDirectoryPath(); - final db = Platform.isIOS - ? await getApplicationSupportDirectory() - : (await getApplicationDocumentsDirectory()); - final dbfile = File('${db.path}/user.db'); + if (selectedPath == null) { + return; + } - final extpath = '$selectedPath/bookmark.db'; + final extpath = '$selectedPath/bookmark.db'; - await dbfile.copy(extpath); + await bookmarkDatabaseFile.copy(extpath); + } flutterToast.showToast( child: ToastWrapper( diff --git a/lib/pages/viewer/others/preload_page_view.dart b/lib/pages/viewer/others/preload_page_view.dart index fa2fe353b..6254c4812 100644 --- a/lib/pages/viewer/others/preload_page_view.dart +++ b/lib/pages/viewer/others/preload_page_view.dart @@ -1014,7 +1014,7 @@ class PageScrollPhysics extends ScrollPhysics { (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) { return super.createBallisticSimulation(position, velocity); } - final Tolerance tolerance = this.tolerance; + final Tolerance tolerance = toleranceFor(position); final double target = _getTargetPixels(position as ScrollPosition, tolerance, velocity); if (target != position.pixels) { diff --git a/lib/platform/misc.dart b/lib/platform/misc.dart index 886c33a7c..7587e65c9 100644 --- a/lib/platform/misc.dart +++ b/lib/platform/misc.dart @@ -16,4 +16,23 @@ class PlatformMiscMethods { await _methodChannel.invokeMethod('finishMainActivity'); } + + Future exportFile( + String filePath, { + required String mimeType, + required String fileNameToSaveAs, + }) async { + if (!Platform.isAndroid) { + throw UnsupportedError('Android only'); + } + + await _methodChannel.invokeMethod( + 'exportFile', + { + 'filePath': filePath, + 'mimeType': mimeType, + 'fileNameToSaveAs': fileNameToSaveAs, + }, + ); + } } diff --git a/webview-viewer/yarn.lock b/webview-viewer/yarn.lock index beeeab59e..a9aa5fa51 100644 --- a/webview-viewer/yarn.lock +++ b/webview-viewer/yarn.lock @@ -31,6 +31,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.22.13": + version: 7.22.13 + resolution: "@babel/code-frame@npm:7.22.13" + dependencies: + "@babel/highlight": ^7.22.13 + chalk: ^2.4.2 + checksum: 22e342c8077c8b77eeb11f554ecca2ba14153f707b85294fcf6070b6f6150aae88a7b7436dd88d8c9289970585f3fe5b9b941c5aa3aa26a6d5a8ef3f292da058 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.20.0": version: 7.20.1 resolution: "@babel/compat-data@npm:7.20.1" @@ -61,7 +71,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.20.1, @babel/generator@npm:^7.20.2": +"@babel/generator@npm:^7.20.2": version: 7.20.4 resolution: "@babel/generator@npm:7.20.4" dependencies: @@ -72,6 +82,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/generator@npm:7.23.0" + dependencies: + "@babel/types": ^7.23.0 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 8efe24adad34300f1f8ea2add420b28171a646edc70f2a1b3e1683842f23b8b7ffa7e35ef0119294e1901f45bfea5b3dc70abe1f10a1917ccdfb41bed69be5f1 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.16.0, @babel/helper-annotate-as-pure@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" @@ -102,22 +124,29 @@ __metadata: languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/helper-function-name@npm:7.19.0" +"@babel/helper-environment-visitor@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-environment-visitor@npm:7.22.20" + checksum: d80ee98ff66f41e233f36ca1921774c37e88a803b2f7dca3db7c057a5fea0473804db9fb6729e5dbfd07f4bed722d60f7852035c2c739382e84c335661590b69 + languageName: node + linkType: hard + +"@babel/helper-function-name@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/helper-function-name@npm:7.23.0" dependencies: - "@babel/template": ^7.18.10 - "@babel/types": ^7.19.0 - checksum: eac1f5db428ba546270c2b8d750c24eb528b8fcfe50c81de2e0bdebf0e20f24bec688d4331533b782e4a907fad435244621ca2193cfcf80a86731299840e0f6e + "@babel/template": ^7.22.15 + "@babel/types": ^7.23.0 + checksum: e44542257b2d4634a1f979244eb2a4ad8e6d75eb6761b4cfceb56b562f7db150d134bc538c8e6adca3783e3bc31be949071527aa8e3aab7867d1ad2d84a26e10 languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-hoist-variables@npm:7.18.6" +"@babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f + "@babel/types": ^7.22.5 + checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc languageName: node linkType: hard @@ -171,6 +200,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" + dependencies: + "@babel/types": ^7.22.5 + checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.19.4": version: 7.19.4 resolution: "@babel/helper-string-parser@npm:7.19.4" @@ -178,6 +216,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -185,6 +230,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-validator-option@npm:7.18.6" @@ -214,7 +266,18 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.18.10, @babel/parser@npm:^7.20.1, @babel/parser@npm:^7.20.2": +"@babel/highlight@npm:^7.22.13": + version: 7.22.20 + resolution: "@babel/highlight@npm:7.22.20" + dependencies: + "@babel/helper-validator-identifier": ^7.22.20 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + checksum: 84bd034dca309a5e680083cd827a766780ca63cef37308404f17653d32366ea76262bd2364b2d38776232f2d01b649f26721417d507e8b4b6da3e4e739f6d134 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.18.10, @babel/parser@npm:^7.20.2": version: 7.20.3 resolution: "@babel/parser@npm:7.20.3" bin: @@ -223,6 +286,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/parser@npm:7.23.0" + bin: + parser: ./bin/babel-parser.js + checksum: 453fdf8b9e2c2b7d7b02139e0ce003d1af21947bbc03eb350fb248ee335c9b85e4ab41697ddbdd97079698de825a265e45a0846bb2ed47a2c7c1df833f42a354 + languageName: node + linkType: hard + "@babel/plugin-syntax-jsx@npm:^7.17.12, @babel/plugin-syntax-jsx@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-syntax-jsx@npm:7.18.6" @@ -312,21 +384,32 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.20.1, @babel/traverse@npm:^7.4.5": - version: 7.20.1 - resolution: "@babel/traverse@npm:7.20.1" +"@babel/template@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/template@npm:7.22.15" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/generator": ^7.20.1 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-function-name": ^7.19.0 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.20.1 - "@babel/types": ^7.20.0 + "@babel/code-frame": ^7.22.13 + "@babel/parser": ^7.22.15 + "@babel/types": ^7.22.15 + checksum: 1f3e7dcd6c44f5904c184b3f7fe280394b191f2fed819919ffa1e529c259d5b197da8981b6ca491c235aee8dbad4a50b7e31304aa531271cb823a4a24a0dd8fd + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.20.1, @babel/traverse@npm:^7.4.5": + version: 7.23.2 + resolution: "@babel/traverse@npm:7.23.2" + dependencies: + "@babel/code-frame": ^7.22.13 + "@babel/generator": ^7.23.0 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.23.0 + "@babel/types": ^7.23.0 debug: ^4.1.0 globals: ^11.1.0 - checksum: 6696176d574b7ff93466848010bc7e94b250169379ec2a84f1b10da46a7cc2018ea5e3a520c3078487db51e3a4afab9ecff48f25d1dbad8c1319362f4148fb4b + checksum: 26a1eea0dde41ab99dde8b9773a013a0dc50324e5110a049f5d634e721ff08afffd54940b3974a20308d7952085ac769689369e9127dea655f868c0f6e1ab35d languageName: node linkType: hard @@ -341,6 +424,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/types@npm:7.23.0" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 215fe04bd7feef79eeb4d33374b39909ce9cad1611c4135a4f7fdf41fe3280594105af6d7094354751514625ea92d0875aba355f53e86a92600f290e77b0e604 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.10.5": version: 11.10.5 resolution: "@emotion/babel-plugin@npm:11.10.5" @@ -633,6 +727,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.1 + resolution: "@jridgewell/resolve-uri@npm:3.1.1" + checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 + languageName: node + linkType: hard + "@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" @@ -647,6 +748,23 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.17": + version: 0.3.20 + resolution: "@jridgewell/trace-mapping@npm:0.3.20" + dependencies: + "@jridgewell/resolve-uri": ^3.1.0 + "@jridgewell/sourcemap-codec": ^1.4.14 + checksum: cd1a7353135f385909468ff0cf20bdd37e59f2ee49a13a966dedf921943e222082c583ade2b579ff6cd0d8faafcb5461f253e1bf2a9f48fec439211fdbe788f5 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.17 resolution: "@jridgewell/trace-mapping@npm:0.3.17" @@ -1819,7 +1937,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0": +"chalk@npm:^2.0.0, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: