()
5 |
6 | fun addListener(listener: Listener) {
7 | listeners.add(listener)
8 | }
9 |
10 | fun removeListener(listener: Listener) {
11 | listeners.remove(listener)
12 | }
13 |
14 | interface Listener {
15 | fun onLog(level: Int, time: String, msg: String)
16 | }
17 |
18 | fun log(level: Int, time: String, msg: String) {
19 | listeners.forEach {
20 | it.onLog(level, time, msg)
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/AndroidUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.alistflutter.utils
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Context.RECEIVER_EXPORTED
6 | import android.content.IntentFilter
7 | import android.os.Build
8 |
9 | object AndroidUtils {
10 | fun Context.registerReceiverCompat(
11 | receiver: BroadcastReceiver,
12 | vararg actions: String
13 | ) {
14 | val intentFilter = IntentFilter()
15 | actions.forEach { intentFilter.addAction(it) }
16 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
17 | registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED)
18 | } else {
19 | registerReceiver(receiver, intentFilter)
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/ClipBoardUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.alistflutter.utils
2 |
3 | import android.content.ClipData
4 | import android.content.ClipboardManager
5 | import android.content.ClipboardManager.OnPrimaryClipChangedListener
6 | import android.content.Context
7 | import com.github.jing332.alistflutter.app
8 |
9 |
10 | /**
11 | *
12 | * author: Blankj
13 | * blog : http://blankj.com
14 | * time : 2016/09/25
15 | * desc : utils about clipboard
16 |
*
17 | */
18 | object ClipboardUtils {
19 |
20 | /**
21 | * Copy the text to clipboard.
22 | *
23 | * The label equals name of package.
24 | *
25 | * @param text The text.
26 | */
27 | fun copyText(text: CharSequence?) {
28 | val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
29 | cm.setPrimaryClip(ClipData.newPlainText(app.getPackageName(), text))
30 | }
31 |
32 | /**
33 | * Copy the text to clipboard.
34 | *
35 | * @param label The label.
36 | * @param text The text.
37 | */
38 | fun copyText(label: CharSequence?, text: CharSequence?) {
39 | val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
40 | cm.setPrimaryClip(ClipData.newPlainText(label, text))
41 | }
42 |
43 | /**
44 | * Clear the clipboard.
45 | */
46 | fun clear() {
47 | val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
48 | cm.setPrimaryClip(ClipData.newPlainText(null, ""))
49 | }
50 |
51 | /**
52 | * Return the label for clipboard.
53 | *
54 | * @return the label for clipboard
55 | */
56 | fun getLabel(): CharSequence {
57 | val cm = app
58 | .getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
59 | val des = cm.primaryClipDescription ?: return ""
60 | return des.label ?: return ""
61 | }
62 |
63 | /**
64 | * Return the text for clipboard.
65 | *
66 | * @return the text for clipboard
67 | */
68 | val text: CharSequence
69 | get() {
70 | val cm =
71 | app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
72 | val clip = cm.primaryClip
73 | if (clip != null && clip.itemCount > 0) {
74 | val text = clip.getItemAt(0).coerceToText(app)
75 | if (text != null) {
76 | return text
77 | }
78 | }
79 | return ""
80 | }
81 |
82 | /**
83 | * Add the clipboard changed listener.
84 | */
85 | fun addChangedListener(listener: OnPrimaryClipChangedListener?) {
86 | val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
87 | cm.addPrimaryClipChangedListener(listener)
88 | }
89 |
90 | /**
91 | * Remove the clipboard changed listener.
92 | */
93 | fun removeChangedListener(listener: OnPrimaryClipChangedListener?) {
94 | val cm = app.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
95 | cm.removePrimaryClipChangedListener(listener)
96 | }
97 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/FileUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.alistflutter.utils
2 |
3 | import java.io.File
4 | import java.io.InputStream
5 | import java.net.URLConnection
6 |
7 | object FileUtils {
8 | val File.mimeType: String?
9 | get() {
10 | val fileNameMap = URLConnection.getFileNameMap()
11 | return fileNameMap.getContentTypeFor(name)
12 | }
13 |
14 | /**
15 | * 按行读取txt
16 | */
17 | fun InputStream.readAllText(): String {
18 | val bufferedReader = this.bufferedReader()
19 | val buffer = StringBuffer("")
20 | var str: String?
21 | while (bufferedReader.readLine().also { str = it } != null) {
22 | buffer.append(str)
23 | buffer.append("\n")
24 | }
25 | return buffer.toString()
26 | }
27 |
28 | fun copyFolder(src: File, target: File, overwrite: Boolean = true) {
29 | val folder = File(target.absolutePath + File.separator + src.name)
30 | folder.mkdirs()
31 |
32 | src.listFiles()?.forEach {
33 | if (it.isFile) {
34 | val newFile = File(folder.absolutePath + File.separator + it.name)
35 | it.copyTo(newFile, overwrite)
36 | } else if (it.isDirectory) {
37 | copyFolder(it, folder)
38 | }
39 | }
40 |
41 | }
42 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/MyTools.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.alistflutter.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.PendingIntent
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.pm.ShortcutInfo
8 | import android.content.pm.ShortcutManager
9 | import android.graphics.drawable.Icon
10 | import android.net.Uri
11 | import android.os.Build
12 | import android.provider.Settings
13 | import com.github.jing332.alistflutter.utils.ToastUtils.longToast
14 | import splitties.systemservices.powerManager
15 |
16 | object MyTools {
17 | fun Context.isIgnoringBatteryOptimizations(): Boolean {
18 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
19 | powerManager.isIgnoringBatteryOptimizations(packageName)
20 | }
21 |
22 | @SuppressLint("BatteryLife")
23 | fun Context.killBattery() {
24 | runCatching {
25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isIgnoringBatteryOptimizations()) {
26 | startActivity(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
27 | data = Uri.parse("package:$packageName")
28 | })
29 | }
30 | }
31 | }
32 |
33 | /* 添加快捷方式 */
34 | @SuppressLint("UnspecifiedImmutableFlag")
35 | @Suppress("DEPRECATION")
36 | fun addShortcut(
37 | ctx: Context,
38 | name: String,
39 | id: String,
40 | iconResId: Int,
41 | launcherIntent: Intent
42 | ) {
43 | ctx.longToast("如失败 请手动授予权限")
44 | if (Build.VERSION.SDK_INT < 26) { /* Android8.0 */
45 | val addShortcutIntent = Intent("com.android.launcher.action.INSTALL_SHORTCUT")
46 | // 不允许重复创建
47 | addShortcutIntent.putExtra("duplicate", false) // 经测试不是根据快捷方式的名字判断重复的
48 | addShortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name)
49 | addShortcutIntent.putExtra(
50 | Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
51 | Intent.ShortcutIconResource.fromContext(
52 | ctx, iconResId
53 | )
54 | )
55 |
56 | launcherIntent.action = Intent.ACTION_MAIN
57 | launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER)
58 | addShortcutIntent
59 | .putExtra(Intent.EXTRA_SHORTCUT_INTENT, launcherIntent)
60 |
61 | // 发送广播
62 | ctx.sendBroadcast(addShortcutIntent)
63 | } else {
64 | val shortcutManager: ShortcutManager = ctx.getSystemService(ShortcutManager::class.java)
65 | if (shortcutManager.isRequestPinShortcutSupported) {
66 | launcherIntent.action = Intent.ACTION_VIEW
67 | val pinShortcutInfo = ShortcutInfo.Builder(ctx, id)
68 | .setIcon(
69 | Icon.createWithResource(ctx, iconResId)
70 | )
71 | .setIntent(launcherIntent)
72 | .setShortLabel(name)
73 | .build()
74 | val pinnedShortcutCallbackIntent = shortcutManager
75 | .createShortcutResultIntent(pinShortcutInfo)
76 | //Get notified when a shortcut is pinned successfully//
77 | val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
78 | PendingIntent.FLAG_IMMUTABLE
79 | } else {
80 | 0
81 | }
82 | val successCallback = PendingIntent.getBroadcast(
83 | ctx, 0, pinnedShortcutCallbackIntent, pendingIntentFlags
84 | )
85 | shortcutManager.requestPinShortcut(
86 | pinShortcutInfo, successCallback.intentSender
87 | )
88 | }
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/StringUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.alistflutter.utils
2 |
3 | object StringUtils {
4 | private fun paramsParseInternal(params: String): HashMap {
5 | val parameters: HashMap = hashMapOf()
6 | if (params.isBlank()) return parameters
7 |
8 | for (param in params.split("&")) {
9 | val entry = param.split("=".toRegex()).dropLastWhile { it.isEmpty() }
10 | if (entry.size > 1) {
11 | parameters[entry[0]] = entry[1]
12 | } else {
13 | parameters[entry[0]] = ""
14 | }
15 | }
16 | return parameters
17 | }
18 |
19 | fun String.paramsParse() = paramsParseInternal(this)
20 |
21 | fun String.toNumberInt(): Int {
22 | return this.replace(Regex("[^0-9]"), "").toIntOrNull() ?: 0
23 | }
24 |
25 | private val mAnsiRegex = Regex("""\x1b(\[.*?[@-~]|].*?(\x07|\x1b\\))""")
26 | fun String.removeAnsiCodes(): String {
27 | return mAnsiRegex.replace(this, "")
28 | }
29 |
30 | fun String.parseToMap(): Map {
31 | return this.split(";").associate {
32 | val ss = it.trim().split("=")
33 | if (ss.size != 2) return@associate "" to ""
34 |
35 | val key = ss[0]
36 | val value = ss[1]
37 | key.trim() to value.trim()
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/github/jing332/alistflutter/utils/ToastUtils.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.alistflutter.utils
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.annotation.StringRes
6 | import kotlinx.coroutines.DelicateCoroutinesApi
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.GlobalScope
9 | import kotlinx.coroutines.launch
10 |
11 | object ToastUtils {
12 | @OptIn(DelicateCoroutinesApi::class)
13 | fun runMain(block: () -> Unit) {
14 | GlobalScope.launch(Dispatchers.Main) {
15 | block()
16 | }
17 | }
18 |
19 | fun Context.toast(str: String) {
20 | runMain {
21 | Toast.makeText(this, str, Toast.LENGTH_SHORT).show()
22 | }
23 | }
24 |
25 | fun Context.toast(@StringRes strId: Int, vararg args: Any) {
26 | runMain {
27 | Toast.makeText(
28 | this,
29 | getString(strId, *args),
30 | Toast.LENGTH_SHORT
31 | ).show()
32 | }
33 | }
34 |
35 | fun Context.longToast(str: String) {
36 | runMain {
37 | Toast.makeText(this, str, Toast.LENGTH_LONG).show()
38 | }
39 | }
40 |
41 | fun Context.longToast(@StringRes strId: Int) {
42 | runMain {
43 | Toast.makeText(this, strId, Toast.LENGTH_LONG).show()
44 | }
45 | }
46 |
47 | fun Context.longToast(@StringRes strId: Int, vararg args: Any) {
48 | runMain {
49 | Toast.makeText(
50 | this,
51 | getString(strId, *args),
52 | Toast.LENGTH_LONG
53 | ).show()
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/alist_logo.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/alist_switch.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_female.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/server.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/server2.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | AList
4 |
5 | 开源许可
6 | 返回
7 | 未选择文件
8 | ❌ 错误
9 | 确定
10 | 描述
11 | 日志
12 | AList服务器
13 | 添加桌面快捷方式
14 | 关闭
15 | 复制地址
16 | AList运行中
17 | 取消
18 | admin 密码已设为:\n %1$s
19 | admin 密码
20 | 关闭失败:%1$s
21 | 已复制地址
22 | ⚠️启动服务器才可设置admin密码
23 | AList配置
24 | 设置
25 | 密码
26 | 启动中
27 | 关闭中
28 | 开关
29 | 更多选项
30 | 关于
31 | 监听地址
32 | 编辑 config.json
33 | 请至少启用一个服务器!
34 | AList提供者
35 | account
36 | 路径已复制
37 | 检查更新
38 | 启动
39 | 所有文件访问权限
40 | 挂载本地存储时必须打开,否则无权限读写文件。
41 | 读取存储权限
42 | 写入存储权限
43 | 请求电池优化白名单
44 | 如果程序在后台运行时被系统杀死,可以尝试设置。
45 | 关闭
46 | 自动检查更新
47 | 打开程序主界面时从Github检查更新
48 | 唤醒锁
49 | 打开可防止锁屏后CPU休眠,但在部分系统可能会导致杀后台
50 | 重要设置
51 | 打开data文件夹
52 | 点按上方路径选择“MT管理器”打开data文件夹
53 | 网页
54 | 跳转失败: %1$s
55 | 清空网页数据
56 | 清空网页缓存
57 | 清空网页数据库、Cookie、DomStorage。
58 | 仅清空资源缓存,不影响用户数据。
59 | 已清除
60 | 确定
61 | 自动打开网页界面
62 | 打开主界面时,自动跳转到网页界面。
63 | 下载文件
64 | 系统下载器
65 | 打开链接
66 | 已复制链接
67 | 启动中
68 | 已关闭: %1$s
69 | 浏览器
70 | 选择下载器
71 | 上次使用
72 | 开机自启动服务
73 | 在开机时自动开启AList服务。
74 | 关闭失败
75 |
76 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/file_path_data.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext{
3 | kotlin_version = '1.9.21'
4 | agp_version = '8.2.0'
5 | room_version = '2.6.1'
6 | ksp_version = '1.9.21-1.0.16'
7 | }
8 |
9 |
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 |
15 | dependencies {
16 | classpath "com.android.tools.build:gradle:$agp_version"
17 | // classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
18 |
19 | }
20 | }
21 |
22 | plugins {
23 | id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
24 | id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
25 | id 'com.android.application' version "$agp_version" apply false
26 | // id 'com.android.library' version "$agp_version" apply false
27 | id("com.google.devtools.ksp") version "$ksp_version" apply false
28 | }
29 |
30 | allprojects {
31 | repositories {
32 | google()
33 | mavenCentral()
34 | }
35 | }
36 |
37 | rootProject.buildDir = '../build'
38 | subprojects {
39 | project.buildDir = "${rootProject.buildDir}/${project.name}"
40 | }
41 | subprojects {
42 | project.evaluationDependsOn(':app')
43 | }
44 |
45 | tasks.register("clean", Delete) {
46 | delete rootProject.buildDir
47 | }
48 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jan 13 16:28:25 CST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
21 | }
22 | }
23 |
24 | plugins {
25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
26 | id "com.android.application" version "8.2.0" apply false
27 | }
28 |
29 | include ":app"
30 | include ":utils"
--------------------------------------------------------------------------------
/android/utils/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /.cxx
--------------------------------------------------------------------------------
/android/utils/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace "com.github.jing332.utils"
8 | compileSdk 34
9 |
10 | defaultConfig {
11 | // minSdk
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | }
15 |
16 | externalNativeBuild {
17 | cmake {
18 | path "src/main/cpp/CMakeLists.txt"
19 | version "3.22.1"
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | kotlinOptions {
27 | jvmTarget = "1.8"
28 | }
29 | }
30 |
31 | dependencies {
32 | testImplementation("junit:junit:4.13.2")
33 | androidTestImplementation("androidx.test.ext:junit:1.1.5")
34 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
35 | }
--------------------------------------------------------------------------------
/android/utils/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/android/utils/consumer-rules.pro
--------------------------------------------------------------------------------
/android/utils/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/android/utils/src/androidTest/java/com/github/jing332/utils/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.utils
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.github.jing332.utils.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/android/utils/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/android/utils/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html.
3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples.
4 |
5 | # Sets the minimum CMake version required for this project.
6 | cmake_minimum_required(VERSION 3.22.1)
7 |
8 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
9 | # Since this is the top level CMakeLists.txt, the project name is also accessible
10 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
11 | # build script scope).
12 | project("utils")
13 |
14 | # Creates and names a library, sets it as either STATIC
15 | # or SHARED, and provides the relative paths to its source code.
16 | # You can define multiple libraries, and CMake builds them for you.
17 | # Gradle automatically packages shared libraries with your APK.
18 | #
19 | # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
20 | # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
21 | # is preferred for the same purpose.
22 | #
23 | # In order to load a library into your app from Java/Kotlin, you must call
24 | # System.loadLibrary() and pass the name of the library defined here;
25 | # for GameActivity/NativeActivity derived applications, the same library name must be
26 | # used in the AndroidManifest.xml file.
27 | add_library(${CMAKE_PROJECT_NAME} SHARED
28 | # List C/C++ source files with relative paths to this CMakeLists.txt.
29 | utils.cpp)
30 |
31 | # Specifies libraries CMake should link to your target library. You
32 | # can link libraries from various origins, such as libraries defined in this
33 | # build script, prebuilt third-party libraries, or Android system libraries.
34 | target_link_libraries(${CMAKE_PROJECT_NAME}
35 | # List libraries link to the target library
36 | android
37 | log)
--------------------------------------------------------------------------------
/android/utils/src/main/cpp/utils.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #define RUN_SUCCESS 0
14 | #define RUN_FAIL 1
15 |
16 |
17 | int get_local_ip_using_ifconf(char *str_ip)
18 | {
19 | int sock_fd, intrface;
20 | struct ifreq buf[INET_ADDRSTRLEN];
21 | struct ifconf ifc;
22 | char *local_ip = NULL;
23 | int status = RUN_FAIL;
24 |
25 | if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)
26 | {
27 | ifc.ifc_len = sizeof(buf);
28 | ifc.ifc_buf = (caddr_t)buf;
29 | if (!ioctl(sock_fd, SIOCGIFCONF, (char *)&ifc))
30 | {
31 | intrface = ifc.ifc_len/sizeof(struct ifreq);
32 | while (intrface-- > 0)
33 | {
34 | if (!(ioctl(sock_fd, SIOCGIFADDR, (char *)&buf[intrface])))
35 | {
36 | local_ip = NULL;
37 | local_ip = inet_ntoa(((struct sockaddr_in*)(&buf[intrface].ifr_addr))->sin_addr);
38 | if(local_ip)
39 | {
40 | strcpy(str_ip, local_ip);
41 | status = RUN_SUCCESS;
42 | if(strcmp("127.0.0.1", str_ip))
43 | {
44 | break;
45 | }
46 | }
47 |
48 | }
49 | }
50 | }
51 | close(sock_fd);
52 | }
53 | return status;
54 | }
55 |
56 | extern "C" JNIEXPORT jstring JNICALL
57 | Java_com_github_jing332_utils_NativeLib_getLocalIp(
58 | JNIEnv* env,
59 | jobject /* this */) {
60 | std::string hello = "Hello from C++";
61 | char str_ip[INET_ADDRSTRLEN];
62 | int status = get_local_ip_using_ifconf(str_ip);
63 |
64 | return env->NewStringUTF(str_ip);
65 | }
66 |
--------------------------------------------------------------------------------
/android/utils/src/main/java/com/github/jing332/utils/NativeLib.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.utils
2 |
3 | object NativeLib {
4 | external fun getLocalIp(): String
5 |
6 | init {
7 | System.loadLibrary("utils")
8 | }
9 |
10 | }
--------------------------------------------------------------------------------
/android/utils/src/test/java/com/github/jing332/utils/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.utils
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/assets/alist.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/alist.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jing332/AListFlutter/be263b52ecadae34e8f21c60c89c71dc1a74a3d7/images/alist.jpg
--------------------------------------------------------------------------------
/lib/contant/log_level.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | class LogLevel {
4 | static const int panic = 0;
5 | static const int fatal = 1;
6 | static const int error = 2;
7 |
8 | static const int warn = 3;
9 | static const int info = 4;
10 | static const int debug = 5;
11 | static const int trace = 6;
12 |
13 | static Color toColor(int level) {
14 | //Color.fromARGB(a, r, g, b)
15 | return switch(level) {
16 | LogLevel.panic => const Color.fromARGB(255, 255, 0, 0),
17 | LogLevel.fatal => const Color.fromARGB(255, 255, 0, 0),
18 | LogLevel.error => const Color.fromARGB(255, 255, 0, 0),
19 | LogLevel.warn => const Color.fromARGB(255, 255, 165, 0),
20 | LogLevel.info => const Color.fromARGB(255, 0, 0, 255),
21 | LogLevel.debug => const Color.fromARGB(255, 0, 255, 0),
22 | LogLevel.trace => const Color.fromARGB(255, 0, 255, 0),
23 | _ => const Color.fromARGB(255, 0, 0, 0)
24 | };
25 | }
26 |
27 | static String toStr(int level) {
28 | return switch(level) {
29 | LogLevel.panic => "Panic",
30 | LogLevel.fatal => "Fatal",
31 | LogLevel.error => "Error",
32 | LogLevel.warn => "Warn",
33 | LogLevel.info => "Info",
34 | LogLevel.debug => "Debug",
35 | LogLevel.trace => "Trace",
36 | _ => ""
37 | };
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/contant/native_bridge.dart:
--------------------------------------------------------------------------------
1 | import 'package:alist_flutter/generated_api.dart';
2 |
3 | class NativeBridge {
4 | static NativeCommon common = NativeCommon();
5 | static Android android = Android();
6 | static AppConfig appConfig = AppConfig();
7 | }
8 |
--------------------------------------------------------------------------------
/lib/generated/intl/messages_all.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that looks up messages for specific locales by
3 | // delegating to the appropriate library.
4 |
5 | // Ignore issues from commonly used lints in this file.
6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new
7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment
9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
10 | // ignore_for_file:comment_references
11 |
12 | import 'dart:async';
13 |
14 | import 'package:flutter/foundation.dart';
15 | import 'package:intl/intl.dart';
16 | import 'package:intl/message_lookup_by_library.dart';
17 | import 'package:intl/src/intl_helpers.dart';
18 |
19 | import 'messages_en.dart' as messages_en;
20 | import 'messages_zh.dart' as messages_zh;
21 |
22 | typedef Future LibraryLoader();
23 | Map _deferredLibraries = {
24 | 'en': () => new SynchronousFuture(null),
25 | 'zh': () => new SynchronousFuture(null),
26 | };
27 |
28 | MessageLookupByLibrary? _findExact(String localeName) {
29 | switch (localeName) {
30 | case 'en':
31 | return messages_en.messages;
32 | case 'zh':
33 | return messages_zh.messages;
34 | default:
35 | return null;
36 | }
37 | }
38 |
39 | /// User programs should call this before using [localeName] for messages.
40 | Future initializeMessages(String localeName) {
41 | var availableLocale = Intl.verifiedLocale(
42 | localeName, (locale) => _deferredLibraries[locale] != null,
43 | onFailure: (_) => null);
44 | if (availableLocale == null) {
45 | return new SynchronousFuture(false);
46 | }
47 | var lib = _deferredLibraries[availableLocale];
48 | lib == null ? new SynchronousFuture(false) : lib();
49 | initializeInternalMessageLookup(() => new CompositeMessageLookup());
50 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
51 | return new SynchronousFuture(true);
52 | }
53 |
54 | bool _messagesExistFor(String locale) {
55 | try {
56 | return _findExact(locale) != null;
57 | } catch (e) {
58 | return false;
59 | }
60 | }
61 |
62 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) {
63 | var actualLocale =
64 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
65 | if (actualLocale == null) return null;
66 | return _findExact(actualLocale);
67 | }
68 |
--------------------------------------------------------------------------------
/lib/generated/intl/messages_en.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that provides messages for a en locale. All the
3 | // messages from the main program should be duplicated here with the same
4 | // function name.
5 |
6 | // Ignore issues from commonly used lints in this file.
7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
12 |
13 | import 'package:intl/intl.dart';
14 | import 'package:intl/message_lookup_by_library.dart';
15 |
16 | final messages = new MessageLookup();
17 |
18 | typedef String MessageIfAbsent(String messageStr, List args);
19 |
20 | class MessageLookup extends MessageLookupByLibrary {
21 | String get localeName => 'en';
22 |
23 | final messages = _notInlinedMessages(_notInlinedMessages);
24 | static Map _notInlinedMessages(_) => {
25 | "about": MessageLookupByLibrary.simpleMessage("About"),
26 | "appName": MessageLookupByLibrary.simpleMessage("AList"),
27 | "autoCheckForUpdates":
28 | MessageLookupByLibrary.simpleMessage("Auto check for updates"),
29 | "autoCheckForUpdatesDesc": MessageLookupByLibrary.simpleMessage(
30 | "Check for updates when app starts"),
31 | "autoStartWebPage": MessageLookupByLibrary.simpleMessage("将网页设置为打开首页"),
32 | "autoStartWebPageDesc":
33 | MessageLookupByLibrary.simpleMessage("打开主界面时的首页"),
34 | "bootAutoStartService":
35 | MessageLookupByLibrary.simpleMessage("Boot auto-start service"),
36 | "bootAutoStartServiceDesc": MessageLookupByLibrary.simpleMessage(
37 | "Automatically start AList service after boot. (Please make sure to grant auto-start permission)"),
38 | "cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
39 | "checkForUpdates":
40 | MessageLookupByLibrary.simpleMessage("Check for updates"),
41 | "confirm": MessageLookupByLibrary.simpleMessage("OK"),
42 | "copiedToClipboard":
43 | MessageLookupByLibrary.simpleMessage("Copied to clipboard"),
44 | "currentIsLatestVersion":
45 | MessageLookupByLibrary.simpleMessage("Current is latest version"),
46 | "dataDirectory": MessageLookupByLibrary.simpleMessage("data Directory"),
47 | "desktopShortcut":
48 | MessageLookupByLibrary.simpleMessage("Desktop shortcut"),
49 | "download": MessageLookupByLibrary.simpleMessage("download"),
50 | "downloadApk": MessageLookupByLibrary.simpleMessage("Download APK"),
51 | "downloadThisFile":
52 | MessageLookupByLibrary.simpleMessage("Download this file?"),
53 | "general": MessageLookupByLibrary.simpleMessage("General"),
54 | "goTo": MessageLookupByLibrary.simpleMessage("GO"),
55 | "grantManagerStoragePermission": MessageLookupByLibrary.simpleMessage(
56 | "Grant 【Manage external storage】 permission"),
57 | "grantNotificationPermission": MessageLookupByLibrary.simpleMessage(
58 | "Grant 【Notification】 permission"),
59 | "grantNotificationPermissionDesc": MessageLookupByLibrary.simpleMessage(
60 | "Used for foreground service keep alive"),
61 | "grantStoragePermission": MessageLookupByLibrary.simpleMessage(
62 | "Grant 【external storage】 permission"),
63 | "grantStoragePermissionDesc": MessageLookupByLibrary.simpleMessage(
64 | "Mounting local storage is a must, otherwise no permission to read and write files"),
65 | "importantSettings":
66 | MessageLookupByLibrary.simpleMessage("Important settings"),
67 | "jumpToOtherApp":
68 | MessageLookupByLibrary.simpleMessage("Jump to other app?"),
69 | "moreOptions": MessageLookupByLibrary.simpleMessage("More options"),
70 | "releasePage": MessageLookupByLibrary.simpleMessage("Release Page"),
71 | "selectAppToOpen":
72 | MessageLookupByLibrary.simpleMessage("Select app to open"),
73 | "setAdminPassword":
74 | MessageLookupByLibrary.simpleMessage("Set admin password"),
75 | "setDefaultDirectory":
76 | MessageLookupByLibrary.simpleMessage("是否设为默认目录?"),
77 | "settings": MessageLookupByLibrary.simpleMessage("Settings"),
78 | "silentJumpApp":
79 | MessageLookupByLibrary.simpleMessage("silent jump app"),
80 | "silentJumpAppDesc": MessageLookupByLibrary.simpleMessage(
81 | "Jump to other app without prompt"),
82 | "uiSettings": MessageLookupByLibrary.simpleMessage("UI"),
83 | "wakeLock": MessageLookupByLibrary.simpleMessage("Wake lock"),
84 | "wakeLockDesc": MessageLookupByLibrary.simpleMessage(
85 | "Prevent CPU from sleeping when screen is off. (May cause app killed in background on some devices)"),
86 | "webPage": MessageLookupByLibrary.simpleMessage("Web Page")
87 | };
88 | }
89 |
--------------------------------------------------------------------------------
/lib/generated/intl/messages_zh.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that provides messages for a zh locale. All the
3 | // messages from the main program should be duplicated here with the same
4 | // function name.
5 |
6 | // Ignore issues from commonly used lints in this file.
7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
12 |
13 | import 'package:intl/intl.dart';
14 | import 'package:intl/message_lookup_by_library.dart';
15 |
16 | final messages = new MessageLookup();
17 |
18 | typedef String MessageIfAbsent(String messageStr, List args);
19 |
20 | class MessageLookup extends MessageLookupByLibrary {
21 | String get localeName => 'zh';
22 |
23 | final messages = _notInlinedMessages(_notInlinedMessages);
24 | static Map _notInlinedMessages(_) => {
25 | "about": MessageLookupByLibrary.simpleMessage("关于"),
26 | "appName": MessageLookupByLibrary.simpleMessage("AList"),
27 | "autoCheckForUpdates": MessageLookupByLibrary.simpleMessage("自动检查更新"),
28 | "autoCheckForUpdatesDesc":
29 | MessageLookupByLibrary.simpleMessage("启动时自动检查更新"),
30 | "autoStartWebPage": MessageLookupByLibrary.simpleMessage("将网页设置为打开首页"),
31 | "autoStartWebPageDesc":
32 | MessageLookupByLibrary.simpleMessage("打开主界面时的首页"),
33 | "bootAutoStartService": MessageLookupByLibrary.simpleMessage("开机自启动服务"),
34 | "bootAutoStartServiceDesc": MessageLookupByLibrary.simpleMessage(
35 | "在开机后自动启动AList服务。(请确保授予自启动权限)"),
36 | "cancel": MessageLookupByLibrary.simpleMessage("取消"),
37 | "checkForUpdates": MessageLookupByLibrary.simpleMessage("检查更新"),
38 | "confirm": MessageLookupByLibrary.simpleMessage("确认"),
39 | "copiedToClipboard": MessageLookupByLibrary.simpleMessage("已复制到剪贴板"),
40 | "currentIsLatestVersion":
41 | MessageLookupByLibrary.simpleMessage("已经是最新版本"),
42 | "dataDirectory": MessageLookupByLibrary.simpleMessage("data 文件夹路径"),
43 | "desktopShortcut": MessageLookupByLibrary.simpleMessage("桌面快捷方式"),
44 | "download": MessageLookupByLibrary.simpleMessage("下载"),
45 | "downloadApk": MessageLookupByLibrary.simpleMessage("下载APK"),
46 | "downloadThisFile": MessageLookupByLibrary.simpleMessage("下载此文件吗?"),
47 | "general": MessageLookupByLibrary.simpleMessage("通用"),
48 | "goTo": MessageLookupByLibrary.simpleMessage("前往"),
49 | "grantManagerStoragePermission":
50 | MessageLookupByLibrary.simpleMessage("申请【所有文件访问权限】"),
51 | "grantNotificationPermission":
52 | MessageLookupByLibrary.simpleMessage("申请【通知权限】"),
53 | "grantNotificationPermissionDesc":
54 | MessageLookupByLibrary.simpleMessage("用于前台服务保活"),
55 | "grantStoragePermission":
56 | MessageLookupByLibrary.simpleMessage("申请【读写外置存储权限】"),
57 | "grantStoragePermissionDesc":
58 | MessageLookupByLibrary.simpleMessage("挂载本地存储时必须授予,否则无权限读写文件"),
59 | "importantSettings": MessageLookupByLibrary.simpleMessage("重要"),
60 | "jumpToOtherApp": MessageLookupByLibrary.simpleMessage("跳转到其他APP ?"),
61 | "moreOptions": MessageLookupByLibrary.simpleMessage("更多选项"),
62 | "releasePage": MessageLookupByLibrary.simpleMessage("发布页面"),
63 | "selectAppToOpen": MessageLookupByLibrary.simpleMessage("选择应用打开"),
64 | "setAdminPassword": MessageLookupByLibrary.simpleMessage("设置admin密码"),
65 | "setDefaultDirectory":
66 | MessageLookupByLibrary.simpleMessage("是否设为初始目录?"),
67 | "settings": MessageLookupByLibrary.simpleMessage("设置"),
68 | "silentJumpApp": MessageLookupByLibrary.simpleMessage("静默跳转APP"),
69 | "silentJumpAppDesc":
70 | MessageLookupByLibrary.simpleMessage("跳转APP时,不弹出提示框"),
71 | "uiSettings": MessageLookupByLibrary.simpleMessage("界面"),
72 | "wakeLock": MessageLookupByLibrary.simpleMessage("唤醒锁"),
73 | "wakeLockDesc": MessageLookupByLibrary.simpleMessage(
74 | "开启防止锁屏后CPU休眠,保持进程在后台运行。(部分系统可能导致杀后台)"),
75 | "webPage": MessageLookupByLibrary.simpleMessage("网页")
76 | };
77 | }
78 |
--------------------------------------------------------------------------------
/lib/generated/l10n.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | import 'package:flutter/material.dart';
3 | import 'package:intl/intl.dart';
4 | import 'intl/messages_all.dart';
5 |
6 | // **************************************************************************
7 | // Generator: Flutter Intl IDE plugin
8 | // Made by Localizely
9 | // **************************************************************************
10 |
11 | // ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
12 | // ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
13 | // ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes
14 |
15 | class S {
16 | S();
17 |
18 | static S? _current;
19 |
20 | static S get current {
21 | assert(_current != null,
22 | 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.');
23 | return _current!;
24 | }
25 |
26 | static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
27 |
28 | static Future load(Locale locale) {
29 | final name = (locale.countryCode?.isEmpty ?? false)
30 | ? locale.languageCode
31 | : locale.toString();
32 | final localeName = Intl.canonicalizedLocale(name);
33 | return initializeMessages(localeName).then((_) {
34 | Intl.defaultLocale = localeName;
35 | final instance = S();
36 | S._current = instance;
37 |
38 | return instance;
39 | });
40 | }
41 |
42 | static S of(BuildContext context) {
43 | final instance = S.maybeOf(context);
44 | assert(instance != null,
45 | 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?');
46 | return instance!;
47 | }
48 |
49 | static S? maybeOf(BuildContext context) {
50 | return Localizations.of(context, S);
51 | }
52 |
53 | /// `AList`
54 | String get appName {
55 | return Intl.message(
56 | 'AList',
57 | name: 'appName',
58 | desc: '',
59 | args: [],
60 | );
61 | }
62 |
63 | /// `桌面快捷方式`
64 | String get desktopShortcut {
65 | return Intl.message(
66 | '桌面快捷方式',
67 | name: 'desktopShortcut',
68 | desc: '',
69 | args: [],
70 | );
71 | }
72 |
73 | /// `设置admin密码`
74 | String get setAdminPassword {
75 | return Intl.message(
76 | '设置admin密码',
77 | name: 'setAdminPassword',
78 | desc: '',
79 | args: [],
80 | );
81 | }
82 |
83 | /// `更多选项`
84 | String get moreOptions {
85 | return Intl.message(
86 | '更多选项',
87 | name: 'moreOptions',
88 | desc: '',
89 | args: [],
90 | );
91 | }
92 |
93 | /// `检查更新`
94 | String get checkForUpdates {
95 | return Intl.message(
96 | '检查更新',
97 | name: 'checkForUpdates',
98 | desc: '',
99 | args: [],
100 | );
101 | }
102 |
103 | /// `已经是最新版本`
104 | String get currentIsLatestVersion {
105 | return Intl.message(
106 | '已经是最新版本',
107 | name: 'currentIsLatestVersion',
108 | desc: '',
109 | args: [],
110 | );
111 | }
112 |
113 | /// `确认`
114 | String get confirm {
115 | return Intl.message(
116 | '确认',
117 | name: 'confirm',
118 | desc: '',
119 | args: [],
120 | );
121 | }
122 |
123 | /// `取消`
124 | String get cancel {
125 | return Intl.message(
126 | '取消',
127 | name: 'cancel',
128 | desc: '',
129 | args: [],
130 | );
131 | }
132 |
133 | /// `发布页面`
134 | String get releasePage {
135 | return Intl.message(
136 | '发布页面',
137 | name: 'releasePage',
138 | desc: '',
139 | args: [],
140 | );
141 | }
142 |
143 | /// `下载APK`
144 | String get downloadApk {
145 | return Intl.message(
146 | '下载APK',
147 | name: 'downloadApk',
148 | desc: '',
149 | args: [],
150 | );
151 | }
152 |
153 | /// `关于`
154 | String get about {
155 | return Intl.message(
156 | '关于',
157 | name: 'about',
158 | desc: '',
159 | args: [],
160 | );
161 | }
162 |
163 | /// `通用`
164 | String get general {
165 | return Intl.message(
166 | '通用',
167 | name: 'general',
168 | desc: '',
169 | args: [],
170 | );
171 | }
172 |
173 | /// `自动检查更新`
174 | String get autoCheckForUpdates {
175 | return Intl.message(
176 | '自动检查更新',
177 | name: 'autoCheckForUpdates',
178 | desc: '',
179 | args: [],
180 | );
181 | }
182 |
183 | /// `启动时自动检查更新`
184 | String get autoCheckForUpdatesDesc {
185 | return Intl.message(
186 | '启动时自动检查更新',
187 | name: 'autoCheckForUpdatesDesc',
188 | desc: '',
189 | args: [],
190 | );
191 | }
192 |
193 | /// `唤醒锁`
194 | String get wakeLock {
195 | return Intl.message(
196 | '唤醒锁',
197 | name: 'wakeLock',
198 | desc: '',
199 | args: [],
200 | );
201 | }
202 |
203 | /// `开启防止锁屏后CPU休眠,保持进程在后台运行。(部分系统可能导致杀后台)`
204 | String get wakeLockDesc {
205 | return Intl.message(
206 | '开启防止锁屏后CPU休眠,保持进程在后台运行。(部分系统可能导致杀后台)',
207 | name: 'wakeLockDesc',
208 | desc: '',
209 | args: [],
210 | );
211 | }
212 |
213 | /// `开机自启动服务`
214 | String get bootAutoStartService {
215 | return Intl.message(
216 | '开机自启动服务',
217 | name: 'bootAutoStartService',
218 | desc: '',
219 | args: [],
220 | );
221 | }
222 |
223 | /// `在开机后自动启动AList服务。(请确保授予自启动权限)`
224 | String get bootAutoStartServiceDesc {
225 | return Intl.message(
226 | '在开机后自动启动AList服务。(请确保授予自启动权限)',
227 | name: 'bootAutoStartServiceDesc',
228 | desc: '',
229 | args: [],
230 | );
231 | }
232 |
233 | /// `网页`
234 | String get webPage {
235 | return Intl.message(
236 | '网页',
237 | name: 'webPage',
238 | desc: '',
239 | args: [],
240 | );
241 | }
242 |
243 | /// `设置`
244 | String get settings {
245 | return Intl.message(
246 | '设置',
247 | name: 'settings',
248 | desc: '',
249 | args: [],
250 | );
251 | }
252 |
253 | /// `选择应用打开`
254 | String get selectAppToOpen {
255 | return Intl.message(
256 | '选择应用打开',
257 | name: 'selectAppToOpen',
258 | desc: '',
259 | args: [],
260 | );
261 | }
262 |
263 | /// `前往`
264 | String get goTo {
265 | return Intl.message(
266 | '前往',
267 | name: 'goTo',
268 | desc: '',
269 | args: [],
270 | );
271 | }
272 |
273 | /// `下载此文件吗?`
274 | String get downloadThisFile {
275 | return Intl.message(
276 | '下载此文件吗?',
277 | name: 'downloadThisFile',
278 | desc: '',
279 | args: [],
280 | );
281 | }
282 |
283 | /// `下载`
284 | String get download {
285 | return Intl.message(
286 | '下载',
287 | name: 'download',
288 | desc: '',
289 | args: [],
290 | );
291 | }
292 |
293 | /// `已复制到剪贴板`
294 | String get copiedToClipboard {
295 | return Intl.message(
296 | '已复制到剪贴板',
297 | name: 'copiedToClipboard',
298 | desc: '',
299 | args: [],
300 | );
301 | }
302 |
303 | /// `重要`
304 | String get importantSettings {
305 | return Intl.message(
306 | '重要',
307 | name: 'importantSettings',
308 | desc: '',
309 | args: [],
310 | );
311 | }
312 |
313 | /// `界面`
314 | String get uiSettings {
315 | return Intl.message(
316 | '界面',
317 | name: 'uiSettings',
318 | desc: '',
319 | args: [],
320 | );
321 | }
322 |
323 | /// `申请【所有文件访问权限】`
324 | String get grantManagerStoragePermission {
325 | return Intl.message(
326 | '申请【所有文件访问权限】',
327 | name: 'grantManagerStoragePermission',
328 | desc: '',
329 | args: [],
330 | );
331 | }
332 |
333 | /// `挂载本地存储时必须授予,否则无权限读写文件`
334 | String get grantStoragePermissionDesc {
335 | return Intl.message(
336 | '挂载本地存储时必须授予,否则无权限读写文件',
337 | name: 'grantStoragePermissionDesc',
338 | desc: '',
339 | args: [],
340 | );
341 | }
342 |
343 | /// `申请【读写外置存储权限】`
344 | String get grantStoragePermission {
345 | return Intl.message(
346 | '申请【读写外置存储权限】',
347 | name: 'grantStoragePermission',
348 | desc: '',
349 | args: [],
350 | );
351 | }
352 |
353 | /// `申请【通知权限】`
354 | String get grantNotificationPermission {
355 | return Intl.message(
356 | '申请【通知权限】',
357 | name: 'grantNotificationPermission',
358 | desc: '',
359 | args: [],
360 | );
361 | }
362 |
363 | /// `用于前台服务保活`
364 | String get grantNotificationPermissionDesc {
365 | return Intl.message(
366 | '用于前台服务保活',
367 | name: 'grantNotificationPermissionDesc',
368 | desc: '',
369 | args: [],
370 | );
371 | }
372 |
373 | /// `将网页设置为打开首页`
374 | String get autoStartWebPage {
375 | return Intl.message(
376 | '将网页设置为打开首页',
377 | name: 'autoStartWebPage',
378 | desc: '',
379 | args: [],
380 | );
381 | }
382 |
383 | /// `跳转到其他APP ?`
384 | String get jumpToOtherApp {
385 | return Intl.message(
386 | '跳转到其他APP ?',
387 | name: 'jumpToOtherApp',
388 | desc: '',
389 | args: [],
390 | );
391 | }
392 |
393 | /// `打开主界面时的首页`
394 | String get autoStartWebPageDesc {
395 | return Intl.message(
396 | '打开主界面时的首页',
397 | name: 'autoStartWebPageDesc',
398 | desc: '',
399 | args: [],
400 | );
401 | }
402 |
403 | /// `data 文件夹路径`
404 | String get dataDirectory {
405 | return Intl.message(
406 | 'data 文件夹路径',
407 | name: 'dataDirectory',
408 | desc: '',
409 | args: [],
410 | );
411 | }
412 |
413 | /// `是否设为初始目录?`
414 | String get setDefaultDirectory {
415 | return Intl.message(
416 | '是否设为初始目录?',
417 | name: 'setDefaultDirectory',
418 | desc: '',
419 | args: [],
420 | );
421 | }
422 |
423 | /// `静默跳转APP`
424 | String get silentJumpApp {
425 | return Intl.message(
426 | '静默跳转APP',
427 | name: 'silentJumpApp',
428 | desc: '',
429 | args: [],
430 | );
431 | }
432 |
433 | /// `跳转APP时,不弹出提示框`
434 | String get silentJumpAppDesc {
435 | return Intl.message(
436 | '跳转APP时,不弹出提示框',
437 | name: 'silentJumpAppDesc',
438 | desc: '',
439 | args: [],
440 | );
441 | }
442 | }
443 |
444 | class AppLocalizationDelegate extends LocalizationsDelegate {
445 | const AppLocalizationDelegate();
446 |
447 | List get supportedLocales {
448 | return const [
449 | Locale.fromSubtags(languageCode: 'zh'),
450 | Locale.fromSubtags(languageCode: 'en'),
451 | ];
452 | }
453 |
454 | @override
455 | bool isSupported(Locale locale) => _isSupported(locale);
456 | @override
457 | Future load(Locale locale) => S.load(locale);
458 | @override
459 | bool shouldReload(AppLocalizationDelegate old) => false;
460 |
461 | bool _isSupported(Locale locale) {
462 | for (var supportedLocale in supportedLocales) {
463 | if (supportedLocale.languageCode == locale.languageCode) {
464 | return true;
465 | }
466 | }
467 | return false;
468 | }
469 | }
470 |
--------------------------------------------------------------------------------
/lib/l10n/intl_en.arb:
--------------------------------------------------------------------------------
1 | {
2 | "@@locale": "en",
3 | "appName": "AList",
4 | "desktopShortcut": "Desktop shortcut",
5 | "setAdminPassword": "Set admin password",
6 | "moreOptions": "More options",
7 | "checkForUpdates": "Check for updates",
8 | "currentIsLatestVersion": "Current is latest version",
9 | "confirm": "OK",
10 | "cancel": "Cancel",
11 | "releasePage": "Release Page",
12 | "downloadApk": "Download APK",
13 | "about": "About",
14 | "general": "General",
15 | "autoCheckForUpdates": "Auto check for updates",
16 | "autoCheckForUpdatesDesc": "Check for updates when app starts",
17 | "wakeLock": "Wake lock",
18 | "wakeLockDesc": "Prevent CPU from sleeping when screen is off. (May cause app killed in background on some devices)",
19 | "bootAutoStartService": "Boot auto-start service",
20 | "bootAutoStartServiceDesc": "Automatically start AList service after boot. (Please make sure to grant auto-start permission)",
21 | "webPage": "Web Page",
22 | "settings": "Settings",
23 | "jumpToOtherApp": "Jump to other app?",
24 | "selectAppToOpen": "Select app to open",
25 | "goTo": "GO",
26 | "downloadThisFile": "Download this file?",
27 | "download": "download",
28 | "copiedToClipboard": "Copied to clipboard",
29 | "importantSettings": "Important settings",
30 | "uiSettings": "UI",
31 | "grantManagerStoragePermission": "Grant 【Manage external storage】 permission",
32 | "grantStoragePermissionDesc": "Mounting local storage is a must, otherwise no permission to read and write files",
33 | "grantStoragePermission": "Grant 【external storage】 permission",
34 | "grantNotificationPermission": "Grant 【Notification】 permission",
35 | "grantNotificationPermissionDesc": "Used for foreground service keep alive",
36 | "autoStartWebPage": "将网页设置为打开首页",
37 | "autoStartWebPageDesc": "打开主界面时的首页",
38 | "dataDirectory": "data Directory",
39 | "setDefaultDirectory": "是否设为默认目录?",
40 | "silentJumpApp": "silent jump app",
41 | "silentJumpAppDesc": "Jump to other app without prompt"
42 | }
--------------------------------------------------------------------------------
/lib/l10n/intl_zh.arb:
--------------------------------------------------------------------------------
1 | {
2 | "@@locale": "zh",
3 | "appName": "AList",
4 | "desktopShortcut": "桌面快捷方式",
5 | "setAdminPassword": "设置admin密码",
6 | "moreOptions": "更多选项",
7 | "checkForUpdates": "检查更新",
8 | "currentIsLatestVersion": "已经是最新版本",
9 | "confirm": "确认",
10 | "cancel": "取消",
11 | "releasePage": "发布页面",
12 | "downloadApk": "下载APK",
13 | "about": "关于",
14 | "general": "通用",
15 | "autoCheckForUpdates": "自动检查更新",
16 | "autoCheckForUpdatesDesc": "启动时自动检查更新",
17 | "wakeLock": "唤醒锁",
18 | "wakeLockDesc": "开启防止锁屏后CPU休眠,保持进程在后台运行。(部分系统可能导致杀后台)",
19 | "bootAutoStartService": "开机自启动服务",
20 | "bootAutoStartServiceDesc": "在开机后自动启动AList服务。(请确保授予自启动权限)",
21 | "webPage": "网页",
22 | "settings": "设置",
23 | "selectAppToOpen": "选择应用打开",
24 | "goTo": "前往",
25 | "downloadThisFile": "下载此文件吗?",
26 | "download": "下载",
27 | "copiedToClipboard": "已复制到剪贴板",
28 | "importantSettings": "重要",
29 | "uiSettings": "界面",
30 | "grantManagerStoragePermission": "申请【所有文件访问权限】",
31 | "grantStoragePermissionDesc": "挂载本地存储时必须授予,否则无权限读写文件",
32 | "grantStoragePermission": "申请【读写外置存储权限】",
33 | "grantNotificationPermission": "申请【通知权限】",
34 | "grantNotificationPermissionDesc": "用于前台服务保活",
35 | "autoStartWebPage": "将网页设置为打开首页",
36 | "jumpToOtherApp": "跳转到其他APP ?",
37 | "autoStartWebPageDesc": "打开主界面时的首页",
38 | "dataDirectory": "data 文件夹路径",
39 | "setDefaultDirectory": "是否设为初始目录?",
40 | "silentJumpApp": "静默跳转APP",
41 | "silentJumpAppDesc": "跳转APP时,不弹出提示框"
42 | }
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 |
3 | import 'package:alist_flutter/generated/l10n.dart';
4 | import 'package:alist_flutter/generated_api.dart';
5 | import 'package:alist_flutter/pages/alist/alist.dart';
6 | import 'package:alist_flutter/pages/app_update_dialog.dart';
7 | import 'package:alist_flutter/pages/settings/settings.dart';
8 | import 'package:alist_flutter/pages/web/web.dart';
9 | import 'package:fade_indexed_stack/fade_indexed_stack.dart';
10 | import 'package:flutter/foundation.dart';
11 | import 'package:flutter/material.dart';
12 | import 'package:flutter_inappwebview/flutter_inappwebview.dart';
13 | import 'package:flutter_localizations/flutter_localizations.dart';
14 | import 'package:flutter_svg/svg.dart';
15 | import 'package:get/get.dart';
16 |
17 | import 'contant/native_bridge.dart';
18 |
19 | void main() async {
20 | WidgetsFlutterBinding.ensureInitialized();
21 | // Android
22 | if (!kIsWeb &&
23 | kDebugMode &&
24 | defaultTargetPlatform == TargetPlatform.android) {
25 | await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode);
26 | }
27 |
28 | runApp(const MyApp());
29 | }
30 |
31 | class MyApp extends StatelessWidget {
32 | const MyApp({super.key});
33 |
34 | // This widget is the root of your application.
35 | @override
36 | Widget build(BuildContext context) {
37 | return GetMaterialApp(
38 | title: 'AListFlutter',
39 | themeMode: ThemeMode.system,
40 | theme: ThemeData(
41 | useMaterial3: true,
42 | colorSchemeSeed: Colors.blueGrey,
43 | inputDecorationTheme: const InputDecorationTheme(
44 | border: OutlineInputBorder(),
45 | ),
46 | ),
47 | darkTheme:ThemeData(
48 | useMaterial3: true,
49 | brightness: Brightness.dark,
50 | colorSchemeSeed: Colors.blueGrey,
51 | /* dark theme settings */
52 | ),
53 | supportedLocales: S.delegate.supportedLocales,
54 | localizationsDelegates: const [
55 | S.delegate,
56 | GlobalMaterialLocalizations.delegate,
57 | GlobalWidgetsLocalizations.delegate,
58 | GlobalCupertinoLocalizations.delegate,
59 | ],
60 | home: const MyHomePage(title: ""),
61 | );
62 | }
63 | }
64 |
65 | class MyHomePage extends StatelessWidget {
66 | const MyHomePage({super.key, required this.title});
67 |
68 | // This widget is the home page of your application. It is stateful, meaning
69 | // that it has a State object (defined below) that contains fields that affect
70 | // how it looks.
71 |
72 | // This class is the configuration for the state. It holds the values (in this
73 | // case the title) provided by the parent (in this case the App widget) and
74 | // used by the build method of the State. Fields in a Widget subclass are
75 | // always marked "final".
76 |
77 | final String title;
78 | static const webPageIndex = 0;
79 |
80 | @override
81 | Widget build(BuildContext context) {
82 | final controller = Get.put(_MainController());
83 |
84 | return Scaffold(
85 | body: Obx(
86 | () => FadeIndexedStack(
87 | lazy: true,
88 | index: controller.selectedIndex.value,
89 | children: [
90 | WebScreen(key: webGlobalKey),
91 | const AListScreen(),
92 | const SettingsScreen()
93 | ],
94 | ),
95 | ),
96 | bottomNavigationBar: Obx(() => NavigationBar(
97 | destinations: [
98 | NavigationDestination(
99 | icon: const Icon(Icons.preview),
100 | label: S.current.webPage,
101 | ),
102 | NavigationDestination(
103 | icon: SvgPicture.asset(
104 | "assets/alist.svg",
105 | color: Theme.of(context).hintColor,
106 | width: 32,
107 | height: 32,
108 | ),
109 | label: S.current.appName,
110 | ),
111 | NavigationDestination(
112 | icon: const Icon(Icons.settings),
113 | label: S.current.settings,
114 | ),
115 | ],
116 | selectedIndex: controller.selectedIndex.value,
117 | onDestinationSelected: (int index) {
118 | // Web
119 | if (controller.selectedIndex.value == webPageIndex &&
120 | controller.selectedIndex.value == webPageIndex) {
121 | webGlobalKey.currentState?.onClickNavigationBar();
122 | }
123 |
124 | controller.setPageIndex(index);
125 | })));
126 | }
127 | }
128 |
129 | class _MainController extends GetxController {
130 | final selectedIndex = 1.obs;
131 |
132 | setPageIndex(int index) {
133 | selectedIndex.value = index;
134 | }
135 |
136 | @override
137 | void onInit() async {
138 | final webPage = await NativeBridge.appConfig.isAutoOpenWebPageEnabled();
139 | if (webPage) {
140 | setPageIndex(MyHomePage.webPageIndex);
141 | }
142 |
143 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
144 | if (await NativeBridge.appConfig.isAutoCheckUpdateEnabled()) {
145 | AppUpdateDialog.checkUpdateAndShowDialog(Get.context!, null);
146 | }
147 | });
148 |
149 | super.onInit();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/lib/pages/alist/about_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ffi';
2 |
3 | import 'package:alist_flutter/contant/native_bridge.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:flutter_svg/svg.dart';
7 | import 'package:get/get.dart';
8 |
9 | import '../../generated/l10n.dart';
10 | import '../../generated_api.dart';
11 | import '../../utils/intent_utils.dart';
12 |
13 | class AppAboutDialog extends StatefulWidget {
14 | const AppAboutDialog({super.key});
15 |
16 | @override
17 | State createState() {
18 | return _AppAboutDialogState();
19 | }
20 | }
21 |
22 | class _AppAboutDialogState extends State {
23 | String _alistVersion = "";
24 | String _version = "";
25 | int _versionCode = 0;
26 |
27 | Future updateVer() async {
28 | _alistVersion = await Android().getAListVersion();
29 | _version = await NativeBridge.common.getVersionName();
30 | _versionCode = await NativeBridge.common.getVersionCode();
31 | return null;
32 | }
33 |
34 | @override
35 | void initState() {
36 | updateVer().then((value) => setState(() {}));
37 |
38 | super.initState();
39 | }
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | final alistUrl =
44 | "https://github.com/alist-org/alist/releases/tag/$_alistVersion";
45 | final appUrl =
46 | "https://github.com/jing332/AListFlutter/releases/tag/$_version";
47 | return AboutDialog(
48 | applicationName: S.of(context).appName,
49 | applicationVersion: '$_version ($_versionCode)',
50 | applicationIcon: SvgPicture.asset(
51 | "assets/alist.svg",
52 | width: 48,
53 | height: 48,
54 | ),
55 | children: [
56 | TextButton(
57 | onPressed: () {
58 | IntentUtils.getUrlIntent(alistUrl).launchChooser("AList");
59 | },
60 | onLongPress: () {
61 | Clipboard.setData(ClipboardData(text: alistUrl));
62 | Get.showSnackbar(GetSnackBar(
63 | message: S.of(context).copiedToClipboard,
64 | duration: const Duration(seconds: 1)));
65 | },
66 | child: const Text("AList"),
67 | ),
68 | TextButton(
69 | onPressed: () {
70 | IntentUtils.getUrlIntent(appUrl).launchChooser("AListFlutter");
71 | },
72 | onLongPress: () {
73 | Clipboard.setData(ClipboardData(text: appUrl));
74 | Get.showSnackbar(GetSnackBar(
75 | message: S.of(context).copiedToClipboard,
76 | duration: const Duration(seconds: 1)));
77 | },
78 | child: const Text("AListFlutter")),
79 | ],
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/lib/pages/alist/alist.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 | import 'dart:io';
3 |
4 | import 'package:alist_flutter/generated_api.dart';
5 | import 'package:alist_flutter/pages/alist/about_dialog.dart';
6 | import 'package:alist_flutter/pages/alist/pwd_edit_dialog.dart';
7 | import 'package:alist_flutter/pages/app_update_dialog.dart';
8 | import 'package:alist_flutter/widgets/switch_floating_action_button.dart';
9 | import 'package:file_picker/file_picker.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:get/get.dart';
12 |
13 | import '../../generated/l10n.dart';
14 | import '../../utils/intent_utils.dart';
15 | import 'log_list_view.dart';
16 |
17 | class AListScreen extends StatelessWidget {
18 | const AListScreen({Key? key}) : super(key: key);
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | final ui = Get.put(AListController());
23 |
24 | return Scaffold(
25 | appBar: AppBar(
26 | backgroundColor: Theme.of(context).colorScheme.primaryContainer,
27 | title: Obx(() => Text("AList - ${ui.alistVersion.value}")),
28 | actions: [
29 | IconButton(
30 | tooltip: S.of(context).desktopShortcut,
31 | onPressed: () async {
32 | Android().addShortcut();
33 | },
34 | icon: const Icon(Icons.add_home),
35 | ),
36 | IconButton(
37 | tooltip: S.current.setAdminPassword,
38 | onPressed: () {
39 | showDialog(
40 | context: context,
41 | builder: (context) => PwdEditDialog(onConfirm: (pwd) {
42 | Get.showSnackbar(GetSnackBar(
43 | title: S.current.setAdminPassword,
44 | message: pwd,
45 | duration: const Duration(seconds: 1)));
46 | Android().setAdminPwd(pwd);
47 | }));
48 | },
49 | icon: const Icon(Icons.password),
50 | ),
51 | PopupMenuButton(
52 | tooltip: S.of(context).moreOptions,
53 | itemBuilder: (context) {
54 | return [
55 | PopupMenuItem(
56 | value: 1,
57 | onTap: () async {
58 | AppUpdateDialog.checkUpdateAndShowDialog(context, (b) {
59 | if (!b) {
60 | Get.showSnackbar(GetSnackBar(
61 | message: S.of(context).currentIsLatestVersion,
62 | duration: const Duration(seconds: 2)));
63 | }
64 | });
65 | },
66 | child: Text(S.of(context).checkForUpdates),
67 | ),
68 | PopupMenuItem(
69 | value: 2,
70 | onTap: () {
71 | showDialog(context: context, builder: ((context){
72 | return const AppAboutDialog();
73 | }));
74 | },
75 | child: Text(S.of(context).about),
76 | ),
77 | ];
78 | },
79 | icon: const Icon(Icons.more_vert),
80 | )
81 | ]),
82 | floatingActionButton: Obx(
83 | () => SwitchFloatingButton(
84 | isSwitch: ui.isSwitch.value,
85 | onSwitchChange: (s) {
86 | ui.clearLog();
87 | ui.isSwitch.value = s;
88 | Android().startService();
89 | }),
90 | ),
91 | body: Obx(() => LogListView(logs: ui.logs.value)));
92 | }
93 | }
94 |
95 | class MyEventReceiver extends Event {
96 | Function(Log log) logCb;
97 | Function(bool isRunning) statusCb;
98 |
99 | MyEventReceiver(this.statusCb, this.logCb);
100 |
101 | @override
102 | void onServiceStatusChanged(bool isRunning) {
103 | statusCb(isRunning);
104 | }
105 |
106 | @override
107 | void onServerLog(int level, String time, String log) {
108 | logCb(Log(level, time, log));
109 | }
110 | }
111 |
112 | class AListController extends GetxController {
113 | final ScrollController _scrollController = ScrollController();
114 | var isSwitch = false.obs;
115 | var alistVersion = "".obs;
116 |
117 | var logs = [].obs;
118 |
119 | void clearLog() {
120 | logs.clear();
121 | }
122 |
123 | void addLog(Log log) {
124 | logs.add(log);
125 | _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
126 | }
127 |
128 | @override
129 | void onInit() {
130 | Event.setup(MyEventReceiver(
131 | (isRunning) => isSwitch.value = isRunning, (log) => addLog(log)));
132 | Android().getAListVersion().then((value) => alistVersion.value = value);
133 | Android().isRunning().then((value) => isSwitch.value = value);
134 |
135 | super.onInit();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/lib/pages/alist/log_level_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 |
3 | import '../../contant/log_level.dart';
4 |
5 | class LogLevelView extends StatefulWidget {
6 | final int level;
7 |
8 | const LogLevelView({super.key, required this.level});
9 |
10 | @override
11 | State createState() => _LogLevelViewState();
12 | }
13 |
14 | class _LogLevelViewState extends State {
15 | @override
16 | Widget build(BuildContext context) {
17 | final s = LogLevel.toStr(widget.level);
18 | final c = LogLevel.toColor(widget.level);
19 | return Text(s, style: TextStyle(color: c));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/pages/alist/log_list_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:alist_flutter/pages/alist/log_level_view.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class Log {
5 | final int level;
6 | final String time;
7 | final String content;
8 |
9 | Log(this.level, this.time, this.content);
10 | }
11 |
12 | class LogListView extends StatefulWidget {
13 | const LogListView({Key? key, required this.logs, this.controller}) : super(key: key);
14 |
15 | final List logs;
16 | final ScrollController? controller;
17 |
18 | @override
19 | State createState() => _LogListViewState();
20 | }
21 |
22 | class _LogListViewState extends State {
23 | @override
24 | Widget build(BuildContext context) {
25 | return ListView.builder(
26 | itemCount: widget.logs.length,
27 | controller: widget.controller,
28 | itemBuilder: (context, index) {
29 | final log = widget.logs[index];
30 | return ListTile(
31 | dense: true,
32 | title: SelectableText(log.content),
33 | subtitle: SelectableText(log.time),
34 | leading: LogLevelView(level: log.level),
35 | );
36 | },
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/pages/alist/pwd_edit_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:get/get.dart';
3 |
4 | class PwdEditDialog extends StatefulWidget {
5 | final ValueChanged onConfirm;
6 |
7 | const PwdEditDialog({super.key, required this.onConfirm});
8 |
9 | @override
10 | State createState() {
11 | return _PwdEditDialogState();
12 | }
13 | }
14 |
15 | class _PwdEditDialogState extends State
16 | with SingleTickerProviderStateMixin {
17 | final TextEditingController pwdController = TextEditingController();
18 |
19 | @override
20 | void dispose() {
21 | pwdController.dispose();
22 | super.dispose();
23 | }
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return AlertDialog(
28 | title: const Text("修改admin密码"),
29 | content: Column(
30 | mainAxisSize: MainAxisSize.min,
31 | children: [
32 | TextField(
33 | controller: pwdController,
34 | decoration: const InputDecoration(
35 | labelText: "admin密码",
36 | ),
37 | ),
38 | ],
39 | ),
40 | actions: [
41 | TextButton(
42 | onPressed: () {Get.back();},
43 | child: const Text("取消"),
44 | ),
45 | FilledButton(
46 | onPressed: () {
47 | Get.back();
48 | widget.onConfirm(pwdController.text);
49 | },
50 | child: const Text("确定"),
51 | ),
52 | ],
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/pages/app_update_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../generated/l10n.dart';
4 | import '../utils/update_checker.dart';
5 | import '../utils/intent_utils.dart';
6 |
7 | class AppUpdateDialog extends StatelessWidget {
8 | final String content;
9 | final String apkUrl;
10 | final String htmlUrl;
11 | final String version;
12 |
13 | const AppUpdateDialog(
14 | {super.key,
15 | required this.content,
16 | required this.apkUrl,
17 | required this.version,
18 | required this.htmlUrl});
19 |
20 | static checkUpdateAndShowDialog(
21 | BuildContext context, ValueChanged? checkFinished) async {
22 | final checker = UpdateChecker(owner: "jing332", repo: "AListFlutter");
23 | await checker.downloadData();
24 | checker.hasNewVersion().then((value) {
25 | checkFinished?.call(value);
26 | if (value) {
27 | showDialog(
28 | context: context,
29 | barrierDismissible: false,
30 | barrierColor: Colors.black.withOpacity(0.5),
31 | builder: (context) {
32 | return AppUpdateDialog(
33 | content: checker.getUpdateContent(),
34 | apkUrl: checker.getApkDownloadUrl(),
35 | htmlUrl: checker.getHtmlUrl(),
36 | version: checker.getTag(),
37 | );
38 | });
39 | }
40 | });
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return AlertDialog(
46 | title: const Text("发现新版本"),
47 | content: Column(mainAxisSize: MainAxisSize.min, children: [
48 | Text("v$version"),
49 | Text(content),
50 | ]),
51 | actions: [
52 | TextButton(
53 | child: Text(S.of(context).cancel),
54 | onPressed: () {
55 | Navigator.pop(context);
56 | },
57 | ),
58 | TextButton(
59 | child: Text(S.of(context).releasePage),
60 | onPressed: () {
61 | Navigator.pop(context);
62 | IntentUtils.getUrlIntent(htmlUrl)
63 | .launchChooser(S.of(context).releasePage);
64 | },
65 | ),
66 | TextButton(
67 | child: Text(S.of(context).downloadApk),
68 | onPressed: () {
69 | Navigator.pop(context);
70 | IntentUtils.getUrlIntent(apkUrl)
71 | .launchChooser(S.of(context).downloadApk);
72 | },
73 | ),
74 | ],
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/pages/settings/preference_widgets.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DividerPreference extends StatelessWidget {
4 | const DividerPreference({super.key, required this.title});
5 |
6 | final String title;
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Column(children: [
11 | const Divider(
12 | height: 1,
13 | ),
14 | Container(
15 | alignment: Alignment.center,
16 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
17 | child: Text(
18 | title,
19 | style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).primaryColor),
20 | ),
21 | ),
22 | ]);
23 | }
24 | }
25 |
26 | class BasicPreference extends StatelessWidget {
27 | final String title;
28 | final String subtitle;
29 | final Widget? leading;
30 | final Widget? trailing;
31 | final GestureTapCallback? onTap;
32 |
33 | const BasicPreference({
34 | super.key,
35 | required this.title,
36 | required this.subtitle,
37 | this.onTap,
38 | this.leading,
39 | this.trailing,
40 | });
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return ListTile(
45 | title: Text(title),
46 | subtitle: Text(subtitle),
47 | leading: leading,
48 | trailing: trailing,
49 | onTap: onTap,
50 | );
51 | }
52 | }
53 |
54 | class SwitchPreference extends StatelessWidget {
55 | const SwitchPreference({
56 | super.key,
57 | required this.title,
58 | required this.subtitle,
59 | this.icon,
60 | required this.value,
61 | required this.onChanged,
62 | });
63 |
64 | final String title;
65 | final String subtitle;
66 | final Widget? icon;
67 | final bool value;
68 | final ValueChanged onChanged;
69 |
70 | @override
71 | Widget build(BuildContext context) {
72 | return BasicPreference(
73 | title: title,
74 | subtitle: subtitle,
75 | leading: icon,
76 | trailing: Switch(value: value, onChanged: onChanged),
77 | onTap: () {
78 | onChanged(!value);
79 | },
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/lib/pages/settings/settings.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 | import 'dart:ffi';
3 |
4 | import 'package:alist_flutter/contant/native_bridge.dart';
5 | import 'package:alist_flutter/generated_api.dart';
6 | import 'package:alist_flutter/pages/settings/preference_widgets.dart';
7 | import 'package:file_picker/file_picker.dart';
8 | import 'package:flutter/material.dart';
9 | import 'package:get/get.dart';
10 | import 'package:permission_handler/permission_handler.dart';
11 |
12 | import '../../generated/l10n.dart';
13 |
14 | class SettingsScreen extends StatefulWidget {
15 | const SettingsScreen({Key? key}) : super(key: key);
16 |
17 | @override
18 | State createState() {
19 | return _SettingsScreenState();
20 | }
21 | }
22 |
23 | class _SettingsScreenState extends State {
24 | late AppLifecycleListener _lifecycleListener;
25 |
26 | @override
27 | void initState() {
28 | _lifecycleListener = AppLifecycleListener(
29 | onResume: () async {
30 | final controller = Get.put(_SettingsController());
31 | controller.updateData();
32 | },
33 | );
34 | super.initState();
35 | }
36 |
37 | @override
38 | void dispose() {
39 | _lifecycleListener.dispose();
40 | super.dispose();
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | final controller = Get.put(_SettingsController());
46 | return Scaffold(
47 | body: Obx(
48 | () => ListView(
49 | children: [
50 | // SizedBox(height: MediaQuery.of(context).padding.top),
51 | Visibility(
52 | visible: !controller._managerStorageGranted.value ||
53 | !controller._notificationGranted.value ||
54 | !controller._storageGranted.value,
55 | child: DividerPreference(title: S.of(context).importantSettings),
56 | ),
57 | Visibility(
58 | visible: !controller._managerStorageGranted.value,
59 | child: BasicPreference(
60 | title: S.of(context).grantManagerStoragePermission,
61 | subtitle: S.of(context).grantStoragePermissionDesc,
62 | onTap: () {
63 | Permission.manageExternalStorage.request();
64 | },
65 | ),
66 | ),
67 | Visibility(
68 | visible: !controller._storageGranted.value,
69 | child: BasicPreference(
70 | title: S.of(context).grantStoragePermission,
71 | subtitle: S.of(context).grantStoragePermissionDesc,
72 | onTap: () {
73 | Permission.storage.request();
74 | },
75 | )),
76 |
77 | Visibility(
78 | visible: !controller._notificationGranted.value,
79 | child: BasicPreference(
80 | title: S.of(context).grantNotificationPermission,
81 | subtitle: S.of(context).grantNotificationPermissionDesc,
82 | onTap: () {
83 | Permission.notification.request();
84 | },
85 | )),
86 |
87 | DividerPreference(title: S.of(context).general),
88 |
89 | SwitchPreference(
90 | title: S.of(context).autoCheckForUpdates,
91 | subtitle: S.of(context).autoCheckForUpdatesDesc,
92 | icon: const Icon(Icons.system_update),
93 | value: controller.autoUpdate,
94 | onChanged: (value) {
95 | controller.autoUpdate = value;
96 | },
97 | ),
98 | SwitchPreference(
99 | title: S.of(context).wakeLock,
100 | subtitle: S.of(context).wakeLockDesc,
101 | icon: const Icon(Icons.screen_lock_portrait),
102 | value: controller.wakeLock,
103 | onChanged: (value) {
104 | controller.wakeLock = value;
105 | },
106 | ),
107 | SwitchPreference(
108 | title: S.of(context).bootAutoStartService,
109 | subtitle: S.of(context).bootAutoStartServiceDesc,
110 | icon: const Icon(Icons.power_settings_new),
111 | value: controller.startAtBoot,
112 | onChanged: (value) {
113 | controller.startAtBoot = value;
114 | },
115 | ),
116 | // AutoStartWebPage
117 | SwitchPreference(
118 | title: S.of(context).autoStartWebPage,
119 | subtitle: S.of(context).autoStartWebPageDesc,
120 | icon: const Icon(Icons.open_in_browser),
121 | value: controller._autoStartWebPage.value,
122 | onChanged: (value) {
123 | controller.autoStartWebPage = value;
124 | },
125 | ),
126 |
127 | BasicPreference(
128 | title: S.of(context).dataDirectory,
129 | subtitle: controller._dataDir.value,
130 | leading: const Icon(Icons.folder),
131 | onTap: () async {
132 | final path = await FilePicker.platform.getDirectoryPath();
133 |
134 | if (path == null) {
135 | Get.showSnackbar(GetSnackBar(
136 | message: S.current.setDefaultDirectory,
137 | duration: const Duration(seconds: 3),
138 | mainButton: TextButton(
139 | onPressed: () {
140 | controller.setDataDir("");
141 | Get.back();
142 | },
143 | child: Text(S.current.confirm),
144 | )));
145 | } else {
146 | controller.setDataDir(path);
147 | }
148 | },
149 | ),
150 | DividerPreference(title: S.of(context).uiSettings),
151 | SwitchPreference(
152 | icon: const Icon(Icons.pan_tool_alt_outlined),
153 | title: S.of(context).silentJumpApp,
154 | subtitle: S.of(context).silentJumpAppDesc,
155 | value: controller._silentJumpApp.value,
156 | onChanged: (value) {
157 | controller.silentJumpApp = value;
158 | })
159 | ],
160 | ),
161 | ));
162 | }
163 | }
164 |
165 | class _SettingsController extends GetxController {
166 | final _dataDir = "".obs;
167 | final _autoUpdate = true.obs;
168 | final _managerStorageGranted = true.obs;
169 | final _notificationGranted = true.obs;
170 | final _storageGranted = true.obs;
171 |
172 | setDataDir(String value) async {
173 | NativeBridge.appConfig.setDataDir(value);
174 | _dataDir.value = await NativeBridge.appConfig.getDataDir();
175 | }
176 |
177 | get dataDir => _dataDir.value;
178 |
179 | set autoUpdate(value) => {
180 | _autoUpdate.value = value,
181 | NativeBridge.appConfig.setAutoCheckUpdateEnabled(value)
182 | };
183 |
184 | get autoUpdate => _autoUpdate.value;
185 |
186 | final _wakeLock = true.obs;
187 |
188 | set wakeLock(value) => {
189 | _wakeLock.value = value,
190 | NativeBridge.appConfig.setWakeLockEnabled(value)
191 | };
192 |
193 | get wakeLock => _wakeLock.value;
194 |
195 | final _autoStart = true.obs;
196 |
197 | set startAtBoot(value) => {
198 | _autoStart.value = value,
199 | NativeBridge.appConfig.setStartAtBootEnabled(value)
200 | };
201 |
202 | get startAtBoot => _autoStart.value;
203 |
204 | final _autoStartWebPage = false.obs;
205 |
206 | set autoStartWebPage(value) => {
207 | _autoStartWebPage.value = value,
208 | NativeBridge.appConfig.setAutoOpenWebPageEnabled(value)
209 | };
210 |
211 | get autoStartWebPage => _autoStartWebPage.value;
212 |
213 | final _silentJumpApp = false.obs;
214 |
215 | get silentJumpApp => _silentJumpApp.value;
216 |
217 | set silentJumpApp(value) => {
218 | _silentJumpApp.value = value,
219 | NativeBridge.appConfig.setSilentJumpAppEnabled(value)
220 | };
221 |
222 | @override
223 | void onInit() async {
224 | updateData();
225 |
226 | super.onInit();
227 | }
228 |
229 | void updateData() async {
230 | final cfg = AppConfig();
231 | cfg.isAutoCheckUpdateEnabled().then((value) => autoUpdate = value);
232 | cfg.isWakeLockEnabled().then((value) => wakeLock = value);
233 | cfg.isStartAtBootEnabled().then((value) => startAtBoot = value);
234 | cfg.isAutoOpenWebPageEnabled().then((value) => autoStartWebPage = value);
235 | cfg.isSilentJumpAppEnabled().then((value) => silentJumpApp = value);
236 |
237 | _dataDir.value = await cfg.getDataDir();
238 |
239 | final sdk = await NativeBridge.common.getDeviceSdkInt();
240 | // A11
241 | if (sdk >= 30) {
242 | _managerStorageGranted.value =
243 | await Permission.manageExternalStorage.isGranted;
244 | } else {
245 | _managerStorageGranted.value = true;
246 | _storageGranted.value = await Permission.storage.isGranted;
247 | }
248 |
249 | // A12
250 | if (sdk >= 32) {
251 | _notificationGranted.value = await Permission.notification.isGranted;
252 | } else {
253 | _notificationGranted.value = true;
254 | }
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/lib/pages/web/web.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 |
3 | import 'package:alist_flutter/contant/native_bridge.dart';
4 | import 'package:alist_flutter/generated_api.dart';
5 | import 'package:alist_flutter/utils/intent_utils.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter/services.dart';
8 | import 'package:flutter_inappwebview/flutter_inappwebview.dart';
9 | import 'package:get/get.dart';
10 |
11 | import '../../generated/l10n.dart';
12 |
13 | GlobalKey webGlobalKey = GlobalKey();
14 |
15 | class WebScreen extends StatefulWidget {
16 | const WebScreen({Key? key}) : super(key: key);
17 |
18 | @override
19 | State createState() {
20 | return WebScreenState();
21 | }
22 | }
23 |
24 | class WebScreenState extends State {
25 | InAppWebViewController? _webViewController;
26 | InAppWebViewSettings settings = InAppWebViewSettings(
27 | allowsInlineMediaPlayback: true,
28 | allowBackgroundAudioPlaying: true,
29 | iframeAllowFullscreen: true,
30 | javaScriptEnabled: true,
31 | mediaPlaybackRequiresUserGesture: false,
32 | useShouldOverrideUrlLoading: true,
33 | );
34 |
35 | double _progress = 0;
36 | String _url = "http://localhost:5244";
37 | bool _canGoBack = false;
38 |
39 | onClickNavigationBar() {
40 | log("onClickNavigationBar");
41 | _webViewController?.reload();
42 | }
43 |
44 | @override
45 | void initState() {
46 | Android()
47 | .getAListHttpPort()
48 | .then((port) => {_url = "http://localhost:$port"});
49 |
50 | // NativeEvent().addServiceStatusListener((isRunning) {
51 | // if (isRunning) _webViewController?.reload();
52 | // });
53 | super.initState();
54 | }
55 |
56 | @override
57 | void dispose() {
58 | _webViewController?.dispose();
59 | super.dispose();
60 | }
61 |
62 | @override
63 | Widget build(BuildContext context) {
64 | return PopScope(
65 | canPop: !_canGoBack,
66 | onPopInvoked: (didPop) async {
67 | log("onPopInvoked $didPop");
68 | if (didPop) return;
69 | _webViewController?.goBack();
70 | },
71 | child: Scaffold(
72 | body: Column(children: [
73 | SizedBox(height: MediaQuery.of(context).padding.top),
74 | LinearProgressIndicator(
75 | value: _progress,
76 | backgroundColor: Colors.grey[200],
77 | valueColor: const AlwaysStoppedAnimation(Colors.blue),
78 | ),
79 | Expanded(
80 | child: InAppWebView(
81 | initialSettings: settings,
82 | initialUrlRequest: URLRequest(url: WebUri(_url)),
83 | onWebViewCreated: (InAppWebViewController controller) {
84 | _webViewController = controller;
85 | },
86 | onLoadStart: (InAppWebViewController controller, Uri? url) {
87 | log("onLoadStart $url");
88 | setState(() {
89 | _progress = 0;
90 | });
91 | },
92 | shouldOverrideUrlLoading: (controller, navigationAction) async {
93 | log("shouldOverrideUrlLoading ${navigationAction.request.url}");
94 |
95 | var uri = navigationAction.request.url!;
96 | if (![
97 | "http",
98 | "https",
99 | "file",
100 | "chrome",
101 | "data",
102 | "javascript",
103 | "about"
104 | ].contains(uri.scheme)) {
105 | log("shouldOverrideUrlLoading ${uri.toString()}");
106 | final silentMode =
107 | await NativeBridge.appConfig.isSilentJumpAppEnabled();
108 | if (silentMode) {
109 | NativeCommon().startActivityFromUri(uri.toString());
110 | } else {
111 | Get.showSnackbar(GetSnackBar(
112 | message: S.current.jumpToOtherApp,
113 | duration: const Duration(seconds: 5),
114 | mainButton: TextButton(
115 | onPressed: () {
116 | NativeCommon()
117 | .startActivityFromUri(uri.toString());
118 | },
119 | child: Text(S.current.goTo),
120 | )));
121 | }
122 |
123 | return NavigationActionPolicy.CANCEL;
124 | }
125 |
126 | return NavigationActionPolicy.ALLOW;
127 | },
128 | onReceivedError: (controller, request, error) async {
129 | if (!await Android().isRunning()) {
130 | await Android().startService();
131 |
132 | for (int i = 0; i < 3; i++) {
133 | await Future.delayed(const Duration(milliseconds: 500));
134 | if (await Android().isRunning()) {
135 | _webViewController?.reload();
136 | break;
137 | }
138 | }
139 | }
140 | },
141 | onDownloadStartRequest: (controller, url) async {
142 | Get.showSnackbar(GetSnackBar(
143 | title: S.of(context).downloadThisFile,
144 | message: url.suggestedFilename ??
145 | url.contentDisposition ??
146 | url.toString(),
147 | duration: const Duration(seconds: 3),
148 | mainButton: Column(children: [
149 | TextButton(
150 | onPressed: () {
151 | IntentUtils.getUrlIntent(url.url.toString())
152 | .launchChooser(S.of(context).selectAppToOpen);
153 | },
154 | child: Text(S.of(context).selectAppToOpen),
155 | ),
156 | TextButton(
157 | onPressed: () {
158 | IntentUtils.getUrlIntent(url.url.toString()).launch();
159 | },
160 | child: Text(S.of(context).download),
161 | ),
162 | ]),
163 | onTap: (_) {
164 | Clipboard.setData(
165 | ClipboardData(text: url.url.toString()));
166 | Get.closeCurrentSnackbar();
167 | Get.showSnackbar(GetSnackBar(
168 | message: S.of(context).copiedToClipboard,
169 | duration: const Duration(seconds: 1),
170 | ));
171 | },
172 | ));
173 | },
174 | onLoadStop:
175 | (InAppWebViewController controller, Uri? url) async {
176 | setState(() {
177 | _progress = 0;
178 | });
179 | },
180 | onProgressChanged:
181 | (InAppWebViewController controller, int progress) {
182 | setState(() {
183 | _progress = progress / 100;
184 | if (_progress == 1) _progress = 0;
185 | });
186 | controller.canGoBack().then((value) => setState(() {
187 | _canGoBack = value;
188 | }));
189 | },
190 | onUpdateVisitedHistory: (InAppWebViewController controller,
191 | WebUri? url, bool? isReload) {
192 | _url = url.toString();
193 | },
194 | ),
195 | ),
196 | ]),
197 | ));
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/lib/utils/intent_utils.dart:
--------------------------------------------------------------------------------
1 | import 'package:android_intent_plus/android_intent.dart';
2 |
3 | class IntentUtils {
4 | static AndroidIntent getUrlIntent(String url) {
5 | return AndroidIntent(action: "action_view", data: url);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/utils/update_checker.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:core';
3 | import 'dart:developer';
4 | import 'dart:io';
5 |
6 | import 'package:alist_flutter/contant/native_bridge.dart';
7 | import 'package:alist_flutter/generated_api.dart';
8 |
9 | class UpdateChecker {
10 | String owner;
11 | String repo;
12 |
13 | Map? _data;
14 |
15 | UpdateChecker({required this.owner, required this.repo});
16 |
17 | String _versionName = "";
18 | String _systemABI = "";
19 |
20 | downloadData() async {
21 | _data = await _getLatestRelease(owner, repo);
22 | _versionName = await NativeBridge.common.getVersionName();
23 | _systemABI = await NativeBridge.common.getDeviceCPUABI();
24 | }
25 |
26 | Map get data {
27 | if (_data == null) {
28 | throw Exception('Data not downloaded');
29 | }
30 | return _data!;
31 | }
32 |
33 | static Future