export all images to albums
This commit is contained in:
parent
9e2027ef45
commit
e3af9c25f1
|
@ -1,48 +1,49 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="niuhuan.pikapi">
|
package="niuhuan.pikapi">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="pikapi"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:label="pikapi"
|
||||||
|
android:requestLegacyExternalStorage="true">
|
||||||
|
<!-- requestLegacyExternalStorage="true" api29 down -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:theme="@style/LaunchTheme"
|
android:hardwareAccelerated="true"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:launchMode="singleTop"
|
||||||
android:hardwareAccelerated="true"
|
android:theme="@style/LaunchTheme"
|
||||||
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
|
||||||
to determine the Window background behind the Flutter UI. -->
|
to determine the Window background behind the Flutter UI. -->
|
||||||
<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" />
|
||||||
/>
|
|
||||||
<!-- Displays an Android View that continues showing the launch screen
|
<!-- Displays an Android View that continues showing the launch screen
|
||||||
Drawable until Flutter paints its first frame, then this splash
|
Drawable until Flutter paints its first frame, then this splash
|
||||||
screen fades out. A splash screen is useful to avoid any visual
|
screen fades out. A splash screen is useful to avoid any visual
|
||||||
gap between the end of Android's launch screen and the painting of
|
gap between the end of Android's launch screen and the painting of
|
||||||
Flutter's first frame. -->
|
Flutter's first frame. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||||
android:resource="@drawable/launch_background"
|
android:resource="@drawable/launch_background" />
|
||||||
/>
|
|
||||||
<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>
|
||||||
<!-- 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>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import android.provider.MediaStore
|
||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
|
import com.google.gson.Gson
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.EventChannel
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
@ -22,7 +23,12 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.newSingleThreadContext
|
import kotlinx.coroutines.newSingleThreadContext
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import mobile.Mobile
|
import mobile.Mobile
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import java.util.concurrent.locks.Condition
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
|
@ -60,8 +66,12 @@ class MainActivity : FlutterActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val resourceQueue: LinkedBlockingQueue<Any?> = LinkedBlockingQueue()
|
||||||
|
private var cacheDir: String? = null
|
||||||
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
cacheDir = context!!.cacheDir.absolutePath
|
||||||
Mobile.initApplication(context!!.filesDir.absolutePath)
|
Mobile.initApplication(context!!.filesDir.absolutePath)
|
||||||
// Method Channel
|
// Method Channel
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "method").setMethodCallHandler { call, result ->
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "method").setMethodCallHandler { call, result ->
|
||||||
|
@ -86,6 +96,9 @@ class MainActivity : FlutterActivity() {
|
||||||
uiMode()
|
uiMode()
|
||||||
}
|
}
|
||||||
"androidGetVersion" -> Build.VERSION.SDK_INT
|
"androidGetVersion" -> Build.VERSION.SDK_INT
|
||||||
|
// "exportComicDownloadAndroidQ" -> {
|
||||||
|
// exportComicDownloadAndroidQ(call.argument("comicId")!!)
|
||||||
|
// }
|
||||||
else -> {
|
else -> {
|
||||||
notImplementedToken
|
notImplementedToken
|
||||||
}
|
}
|
||||||
|
@ -194,7 +207,7 @@ class MainActivity : FlutterActivity() {
|
||||||
private fun setMode(string: String) {
|
private fun setMode(string: String) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
mixDisplay()?.let { display ->
|
mixDisplay()?.let { display ->
|
||||||
if (string == ""){
|
if (string == "") {
|
||||||
uiThreadHandler.post {
|
uiThreadHandler.post {
|
||||||
window.attributes = window.attributes.also { attr ->
|
window.attributes = window.attributes.also { attr ->
|
||||||
attr.preferredDisplayModeId = 0
|
attr.preferredDisplayModeId = 0
|
||||||
|
@ -283,12 +296,63 @@ class MainActivity : FlutterActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 安卓11以上使用了 MANAGE_EXTERNAL_STORAGE 权限来管理整个外置存储 (危险权限)
|
||||||
|
|
||||||
fun main(){
|
// private var tmpComicId: String? = null
|
||||||
startActivityForResult(Intent(Intent.ACTION_GET_CONTENT).also {
|
// private val exportComicDownloadAndroidQRequestCode = 2
|
||||||
it.addCategory(Intent.CATEGORY_OPENABLE)
|
//
|
||||||
},1)
|
// private fun exportComicDownloadAndroidQ(comicId: String) {
|
||||||
}
|
// val title = Mobile.flatInvoke("specialDownloadTitle", comicId)
|
||||||
|
// var fileName = title
|
||||||
|
// fileName = fileName.replace('/', '_')
|
||||||
|
// fileName = fileName.replace('\\', '_')
|
||||||
|
// fileName = fileName.replace('*', '_')
|
||||||
|
// fileName = fileName.replace('?', '_')
|
||||||
|
// fileName = fileName.replace('<', '_')
|
||||||
|
// fileName = fileName.replace('>', '_')
|
||||||
|
// fileName = fileName.replace('|', '_')
|
||||||
|
// fileName = fileName + "_" + System.currentTimeMillis() + ".zip"
|
||||||
|
// tmpComicId = comicId
|
||||||
|
// startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).also {
|
||||||
|
// it.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
// it.type = "application/octet-stream"
|
||||||
|
// it.putExtra(Intent.EXTRA_TITLE, fileName)
|
||||||
|
// }, exportComicDownloadAndroidQRequestCode)
|
||||||
|
// val result = resourceQueue.take()
|
||||||
|
// if (result is Throwable) {
|
||||||
|
// throw result
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
// pool.submit {
|
||||||
|
// try {
|
||||||
|
// if (resultCode === RESULT_OK && data != null) {
|
||||||
|
// when (requestCode) {
|
||||||
|
// exportComicDownloadAndroidQRequestCode -> {
|
||||||
|
// contentResolver.openOutputStream(data.data!!)?.use { os ->
|
||||||
|
// val path = Mobile.flatInvoke("exportComicDownload", Gson().toJson(HashMap<Any, Any?>().also { map ->
|
||||||
|
// map["comicId"] = tmpComicId
|
||||||
|
// map["dir"] = cacheDir
|
||||||
|
// }))
|
||||||
|
// try {
|
||||||
|
// FileInputStream(path).copyTo(os)
|
||||||
|
// } finally {
|
||||||
|
// File(path).delete()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// resourceQueue.put("OK")
|
||||||
|
// }
|
||||||
|
// else -> resourceQueue.put(Exception("WTF"))
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// resourceQueue.put(Exception("NOT OK"))
|
||||||
|
// }
|
||||||
|
// } catch (e: Throwable) {
|
||||||
|
// resourceQueue.put(Exception(e))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"pgo/pikapi/const_value"
|
"pgo/pikapi/const_value"
|
||||||
"pgo/pikapi/database/comic_center"
|
"pgo/pikapi/database/comic_center"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@ func exportComicUsingSocketExit() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportComicDownload(params string) error {
|
func exportComicDownload(params string) (filePath string, err error) {
|
||||||
var paramsStruct struct {
|
var paramsStruct struct {
|
||||||
ComicId string `json:"comicId"`
|
ComicId string `json:"comicId"`
|
||||||
Dir string `json:"dir"`
|
Dir string `json:"dir"`
|
||||||
|
@ -78,29 +79,43 @@ func exportComicDownload(params string) error {
|
||||||
println(fmt.Sprintf("导出 %s 到 %s", comicId, dir))
|
println(fmt.Sprintf("导出 %s 到 %s", comicId, dir))
|
||||||
comic, err := comic_center.FindComicDownloadById(comicId)
|
comic, err := comic_center.FindComicDownloadById(comicId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if comic == nil {
|
if comic == nil {
|
||||||
return errors.New("not found")
|
err = errors.New("not found")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if !comic.DownloadFinished {
|
if !comic.DownloadFinished {
|
||||||
return errors.New("not download finish")
|
err = errors.New("not download finish")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
filePath := path.Join(dir, fmt.Sprintf("%s-%s.zip", comic.Title, time.Now().Format("2006_01_02_15_04_05.999")))
|
filePath = path.Join(dir, fmt.Sprintf("%s-%s.zip", reasonablePath(comic.Title), time.Now().Format("2006_01_02_15_04_05.999")))
|
||||||
println(fmt.Sprintf("ZIP : %s", filePath))
|
println(fmt.Sprintf("ZIP : %s", filePath))
|
||||||
fileStream, err := os.Create(filePath)
|
fileStream, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
defer fileStream.Close()
|
defer fileStream.Close()
|
||||||
zipWriter := zip.NewWriter(fileStream)
|
zipWriter := zip.NewWriter(fileStream)
|
||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
return exportComicDownloadFetch(comicId, func(path string, size int64) (io.Writer, error) {
|
err = exportComicDownloadFetch(comicId, func(path string, size int64) (io.Writer, error) {
|
||||||
header := tar.Header{}
|
header := tar.Header{}
|
||||||
header.Name = path
|
header.Name = path
|
||||||
header.Size = size
|
header.Size = size
|
||||||
return zipWriter.Create(path)
|
return zipWriter.Create(path)
|
||||||
})
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func reasonablePath(title string) string {
|
||||||
|
title = strings.ReplaceAll(title, "\\", "_")
|
||||||
|
title = strings.ReplaceAll(title, "/", "_")
|
||||||
|
title = strings.ReplaceAll(title, "*", "_")
|
||||||
|
title = strings.ReplaceAll(title, "?", "_")
|
||||||
|
title = strings.ReplaceAll(title, "<", "_")
|
||||||
|
title = strings.ReplaceAll(title, ">", "_")
|
||||||
|
title = strings.ReplaceAll(title, "|", "_")
|
||||||
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportComicDownloadFetch(comicId string, onWriteFile func(path string, size int64) (io.Writer, error)) error {
|
func exportComicDownloadFetch(comicId string, onWriteFile func(path string, size int64) (io.Writer, error)) error {
|
||||||
|
@ -367,7 +382,7 @@ func exportComicDownloadToJPG(params string) error {
|
||||||
if !comic.DownloadFinished {
|
if !comic.DownloadFinished {
|
||||||
return errors.New("not download finish")
|
return errors.New("not download finish")
|
||||||
}
|
}
|
||||||
dirPath := path.Join(dir, fmt.Sprintf("%s-%s", comic.Title, time.Now().Format("2006_01_02_15_04_05.999")))
|
dirPath := path.Join(dir, fmt.Sprintf("%s-%s", reasonablePath(comic.Title), time.Now().Format("2006_01_02_15_04_05.999")))
|
||||||
println(fmt.Sprintf("DIR : %s", dirPath))
|
println(fmt.Sprintf("DIR : %s", dirPath))
|
||||||
err = os.Mkdir(dirPath, const_value.CreateDirMode)
|
err = os.Mkdir(dirPath, const_value.CreateDirMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -575,7 +575,7 @@ func FlatInvoke(method string, params string) (string, error) {
|
||||||
case "resetAllDownloads":
|
case "resetAllDownloads":
|
||||||
return "", comic_center.ResetAll()
|
return "", comic_center.ResetAll()
|
||||||
case "exportComicDownload":
|
case "exportComicDownload":
|
||||||
return "", exportComicDownload(params)
|
return exportComicDownload(params)
|
||||||
case "exportComicDownloadToJPG":
|
case "exportComicDownloadToJPG":
|
||||||
return "", exportComicDownloadToJPG(params)
|
return "", exportComicDownloadToJPG(params)
|
||||||
case "exportComicUsingSocket":
|
case "exportComicUsingSocket":
|
||||||
|
@ -599,6 +599,8 @@ func FlatInvoke(method string, params string) (string, error) {
|
||||||
return downloadGame(params)
|
return downloadGame(params)
|
||||||
case "convertImageToJPEG100":
|
case "convertImageToJPEG100":
|
||||||
return "", convertImageToJPEG100(params)
|
return "", convertImageToJPEG100(params)
|
||||||
|
case "specialDownloadTitle":
|
||||||
|
return specialDownloadTitle(params)
|
||||||
}
|
}
|
||||||
return "", errors.New("method not found : " + method)
|
return "", errors.New("method not found : " + method)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import "pgo/pikapi/database/comic_center"
|
||||||
|
|
||||||
|
func specialDownloadTitle(comicId string) (string, error) {
|
||||||
|
info, err := comic_center.DownloadInfo(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return info.Title, nil
|
||||||
|
}
|
|
@ -586,6 +586,20 @@ func DeleteRemoteImages(images []RemoteImage) error {
|
||||||
return db.Unscoped().Model(&RemoteImage{}).Delete("id in ?", ids).Error
|
return db.Unscoped().Model(&RemoteImage{}).Delete("id in ?", ids).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DownloadInfo(comicId string) (*ComicDownload, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var download ComicDownload
|
||||||
|
err := db.First(&download, "id = ?", comicId).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &download, nil
|
||||||
|
}
|
||||||
|
|
||||||
func VACUUM() error {
|
func VACUUM() error {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:filesystem_picker/filesystem_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:pikapi/basic/Common.dart';
|
import 'package:pikapi/basic/Common.dart';
|
||||||
|
import 'package:pikapi/basic/config/Platform.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'Method.dart';
|
import 'Method.dart';
|
||||||
import 'config/ChooserRoot.dart';
|
import 'config/ChooserRoot.dart';
|
||||||
|
@ -57,6 +58,16 @@ Future<dynamic> saveImage(String path, BuildContext context) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> saveImageQuiet(String path, BuildContext context) async {
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
return method.iosSaveFileToImage(path);
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
return _saveImageAndroid(path, context);
|
||||||
|
} else {
|
||||||
|
throw Exception("only mobile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<dynamic> _saveImageAndroid(String path, BuildContext context) async {
|
Future<dynamic> _saveImageAndroid(String path, BuildContext context) async {
|
||||||
var p = await Permission.storage.request();
|
var p = await Permission.storage.request();
|
||||||
if (!p.isGranted) {
|
if (!p.isGranted) {
|
||||||
|
@ -68,9 +79,14 @@ Future<dynamic> _saveImageAndroid(String path, BuildContext context) async {
|
||||||
/// 选择一个文件夹用于保存文件
|
/// 选择一个文件夹用于保存文件
|
||||||
Future<String?> chooseFolder(BuildContext context) async {
|
Future<String?> chooseFolder(BuildContext context) async {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
var p = await Permission.storage.request();
|
if (androidVersion >= 30) {
|
||||||
if (!p.isGranted) {
|
if (!(await Permission.manageExternalStorage.request()).isGranted) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!(await Permission.storage.request()).isGranted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return FilesystemPicker.open(
|
return FilesystemPicker.open(
|
||||||
|
|
|
@ -429,6 +429,12 @@ class Method {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> exportComicDownloadAndroidQ(String comicId) {
|
||||||
|
return _channel.invokeMethod("exportComicDownloadAndroidQ", {
|
||||||
|
"comicId": comicId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<dynamic> exportComicDownloadToJPG(String comicId, String dir) {
|
Future<dynamic> exportComicDownloadToJPG(String comicId, String dir) {
|
||||||
return _flatInvoke("exportComicDownloadToJPG", {
|
return _flatInvoke("exportComicDownloadToJPG", {
|
||||||
"comicId": comicId,
|
"comicId": comicId,
|
||||||
|
|
|
@ -23,7 +23,10 @@ String currentChooserRoot() {
|
||||||
} else if (Platform.isLinux) {
|
} else if (Platform.isLinux) {
|
||||||
return '/';
|
return '/';
|
||||||
} else if (Platform.isAndroid) {
|
} else if (Platform.isAndroid) {
|
||||||
return '/storage/emulated/0/Download';
|
// if (androidVersion >= 30) {
|
||||||
|
// return '/storage/emulated/0/Download';
|
||||||
|
// }
|
||||||
|
return '/storage/emulated/0';
|
||||||
} else {
|
} else {
|
||||||
throw 'error';
|
throw 'error';
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import '../Method.dart';
|
||||||
|
|
||||||
|
int androidVersion = 0;
|
||||||
|
|
||||||
|
Future<void> initPlatform()async{
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
androidVersion = await method.androidGetVersion();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import 'package:event/event.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import '../Method.dart';
|
import '../Method.dart';
|
||||||
|
import 'Platform.dart';
|
||||||
|
|
||||||
// 主题包
|
// 主题包
|
||||||
abstract class _ThemePackage {
|
abstract class _ThemePackage {
|
||||||
|
@ -124,7 +125,6 @@ final _themePackages = <_ThemePackage>[
|
||||||
// 主题更换事件
|
// 主题更换事件
|
||||||
var themeEvent = Event<EventArgs>();
|
var themeEvent = Event<EventArgs>();
|
||||||
|
|
||||||
int _androidVersion = 1;
|
|
||||||
String? _themeCode;
|
String? _themeCode;
|
||||||
ThemeData? _themeData;
|
ThemeData? _themeData;
|
||||||
bool _androidNightMode = false;
|
bool _androidNightMode = false;
|
||||||
|
@ -161,18 +161,15 @@ void _changeThemeByCode(String themeCode) {
|
||||||
const _nightModePropertyName = "androidNightMode";
|
const _nightModePropertyName = "androidNightMode";
|
||||||
|
|
||||||
Future<dynamic> initTheme() async {
|
Future<dynamic> initTheme() async {
|
||||||
if (Platform.isAndroid) {
|
if (androidVersion >= 29) {
|
||||||
_androidVersion = await method.androidGetVersion();
|
_androidNightMode =
|
||||||
if (_androidVersion >= 29) {
|
(await method.loadProperty(_nightModePropertyName, "false")) ==
|
||||||
_androidNightMode =
|
"true";
|
||||||
(await method.loadProperty(_nightModePropertyName, "false")) ==
|
_systemNight = (await method.androidGetUiMode()) == "NIGHT";
|
||||||
"true";
|
EventChannel("ui_mode").receiveBroadcastStream().listen((event) {
|
||||||
_systemNight = (await method.androidGetUiMode()) == "NIGHT";
|
_systemNight = "$event" == "NIGHT";
|
||||||
EventChannel("ui_mode").receiveBroadcastStream().listen((event) {
|
themeEvent.broadcast();
|
||||||
_systemNight = "$event" == "NIGHT";
|
});
|
||||||
themeEvent.broadcast();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_changeThemeByCode(await method.loadTheme());
|
_changeThemeByCode(await method.loadTheme());
|
||||||
}
|
}
|
||||||
|
@ -185,7 +182,7 @@ Future<dynamic> chooseTheme(BuildContext buildContext) async {
|
||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (BuildContext context, StateSetter setState) {
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
var list = <SimpleDialogOption>[];
|
var list = <SimpleDialogOption>[];
|
||||||
if (_androidVersion >= 29) {
|
if (androidVersion >= 29) {
|
||||||
var onChange = (bool? v) async {
|
var onChange = (bool? v) async {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
await method.saveProperty(
|
await method.saveProperty(
|
||||||
|
@ -206,10 +203,10 @@ Future<dynamic> chooseTheme(BuildContext buildContext) async {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(
|
top: BorderSide(
|
||||||
color: Theme
|
color: Theme
|
||||||
.of(context)
|
.of(context)
|
||||||
.dividerColor,
|
.dividerColor,
|
||||||
width: 0.5,
|
width: 0.5,
|
||||||
),
|
),
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: Theme
|
color: Theme
|
||||||
|
|
|
@ -2,10 +2,13 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:pikapi/basic/Channels.dart';
|
import 'package:pikapi/basic/Channels.dart';
|
||||||
|
import 'package:pikapi/basic/Common.dart';
|
||||||
import 'package:pikapi/basic/Cross.dart';
|
import 'package:pikapi/basic/Cross.dart';
|
||||||
import 'package:pikapi/basic/Entities.dart';
|
import 'package:pikapi/basic/Entities.dart';
|
||||||
import 'package:pikapi/basic/Method.dart';
|
import 'package:pikapi/basic/Method.dart';
|
||||||
|
import 'package:pikapi/basic/config/Platform.dart';
|
||||||
import 'package:pikapi/screens/DownloadExportToSocketScreen.dart';
|
import 'package:pikapi/screens/DownloadExportToSocketScreen.dart';
|
||||||
|
|
||||||
import 'components/ContentError.dart';
|
import 'components/ContentError.dart';
|
||||||
|
@ -117,74 +120,118 @@ class _DownloadExportToFileScreenState
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildExportToFileButtons() {
|
List<Widget> _buildExportToFileButtons() {
|
||||||
|
List<Widget> widgets = [];
|
||||||
if (Platform.isWindows ||
|
if (Platform.isWindows ||
|
||||||
Platform.isMacOS ||
|
Platform.isMacOS ||
|
||||||
Platform.isLinux ||
|
Platform.isLinux ||
|
||||||
Platform.isAndroid) {
|
Platform.isAndroid) {
|
||||||
return [
|
widgets.add(MaterialButton(
|
||||||
MaterialButton(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
String? path = await chooseFolder(context);
|
||||||
String? path = await chooseFolder(context);
|
print("path $path");
|
||||||
print("path $path");
|
if (path != null) {
|
||||||
if (path != null) {
|
try {
|
||||||
try {
|
setState(() {
|
||||||
setState(() {
|
exporting = true;
|
||||||
exporting = true;
|
});
|
||||||
});
|
await method.exportComicDownloadToJPG(
|
||||||
await method.exportComicDownloadToJPG(
|
widget.comicId,
|
||||||
widget.comicId,
|
path,
|
||||||
path,
|
);
|
||||||
);
|
setState(() {
|
||||||
setState(() {
|
exportResult = "导出成功";
|
||||||
exportResult = "导出成功";
|
});
|
||||||
});
|
} catch (e) {
|
||||||
} catch (e) {
|
setState(() {
|
||||||
setState(() {
|
exportResult = "导出失败 $e";
|
||||||
exportResult = "导出失败 $e";
|
});
|
||||||
});
|
} finally {
|
||||||
} finally {
|
setState(() {
|
||||||
setState(() {
|
exporting = false;
|
||||||
exporting = false;
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
child: _buildButtonInner('导出到HTML+JPG\n(可直接在相册中打开观看)'),
|
},
|
||||||
),
|
child: _buildButtonInner('导出到HTML+JPG\n(可直接在相册中打开观看)'),
|
||||||
Container(height: 10),
|
));
|
||||||
MaterialButton(
|
widgets.add(Container(height: 10));
|
||||||
onPressed: () async {
|
widgets.add(MaterialButton(
|
||||||
String? path = await chooseFolder(context);
|
onPressed: () async {
|
||||||
print("path $path");
|
String? path = await chooseFolder(context);
|
||||||
if (path != null) {
|
print("path $path");
|
||||||
try {
|
if (path != null) {
|
||||||
setState(() {
|
try {
|
||||||
exporting = true;
|
setState(() {
|
||||||
});
|
exporting = true;
|
||||||
await method.exportComicDownload(
|
});
|
||||||
widget.comicId,
|
await method.exportComicDownload(
|
||||||
path,
|
widget.comicId,
|
||||||
);
|
path,
|
||||||
setState(() {
|
);
|
||||||
exportResult = "导出成功";
|
setState(() {
|
||||||
});
|
exportResult = "导出成功";
|
||||||
} catch (e) {
|
});
|
||||||
setState(() {
|
} catch (e) {
|
||||||
exportResult = "导出失败 $e";
|
setState(() {
|
||||||
});
|
exportResult = "导出失败 $e";
|
||||||
} finally {
|
});
|
||||||
setState(() {
|
} finally {
|
||||||
exporting = false;
|
setState(() {
|
||||||
});
|
exporting = false;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
child: _buildButtonInner('导出到HTML.zip\n(可从其他设备导入 / 解压后可阅读)'),
|
},
|
||||||
),
|
child: _buildButtonInner('导出到HTML.zip\n(可从其他设备导入 / 解压后可阅读)'),
|
||||||
Container(height: 10),
|
));
|
||||||
];
|
widgets.add(Container(height: 10));
|
||||||
}
|
}
|
||||||
return [];
|
if (Platform.isIOS || Platform.isAndroid) {
|
||||||
|
widgets.add(MaterialButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (!(await confirmDialog(context, "导出确认", "将本漫画所有图片到相册?"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(await Permission.storage.request()).isGranted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
exporting = true;
|
||||||
|
});
|
||||||
|
// 导出所有图片数据
|
||||||
|
var count = 0;
|
||||||
|
List<DownloadEp> eps = await method.downloadEpList(widget.comicId);
|
||||||
|
for (var i = 0; i < eps.length; i++) {
|
||||||
|
var pics = await method.downloadPicturesByEpId(eps[i].id);
|
||||||
|
for (var j = 0; j < pics.length; j++) {
|
||||||
|
setState(() {
|
||||||
|
exportMessage = "导出图片 ${count++} 张";
|
||||||
|
});
|
||||||
|
await saveImageQuiet(
|
||||||
|
await method.downloadImagePath(pics[j].localPath),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
exportResult = "导出成功";
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
exportResult = "导出失败 $e";
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
exporting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: _buildButtonInner('将所有图片到处到手机相机'),
|
||||||
|
));
|
||||||
|
widgets.add(Container(height: 10));
|
||||||
|
}
|
||||||
|
return widgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildButtonInner(String text) {
|
Widget _buildButtonInner(String text) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import 'package:pikapi/basic/config/FullScreenAction.dart';
|
||||||
import 'package:pikapi/basic/config/FullScreenUI.dart';
|
import 'package:pikapi/basic/config/FullScreenUI.dart';
|
||||||
import 'package:pikapi/basic/config/KeyboardController.dart';
|
import 'package:pikapi/basic/config/KeyboardController.dart';
|
||||||
import 'package:pikapi/basic/config/PagerAction.dart';
|
import 'package:pikapi/basic/config/PagerAction.dart';
|
||||||
|
import 'package:pikapi/basic/config/Platform.dart';
|
||||||
import 'package:pikapi/basic/config/Proxy.dart';
|
import 'package:pikapi/basic/config/Proxy.dart';
|
||||||
import 'package:pikapi/basic/config/Quality.dart';
|
import 'package:pikapi/basic/config/Quality.dart';
|
||||||
import 'package:pikapi/basic/config/ReaderDirection.dart';
|
import 'package:pikapi/basic/config/ReaderDirection.dart';
|
||||||
|
@ -37,6 +38,7 @@ class _InitScreenState extends State<InitScreen> {
|
||||||
|
|
||||||
Future<dynamic> _init() async {
|
Future<dynamic> _init() async {
|
||||||
// 初始化配置文件
|
// 初始化配置文件
|
||||||
|
await initPlatform(); // 必须第一个初始化, 加载设备信息
|
||||||
await autoClean();
|
await autoClean();
|
||||||
await initAddress();
|
await initAddress();
|
||||||
await initProxy();
|
await initProxy();
|
||||||
|
|
Loading…
Reference in New Issue