├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ │ ├── styles.xml
│ │ │ ├── themes.xml
│ │ │ ├── colors.xml
│ │ │ └── strings.xml
│ │ ├── values-night
│ │ │ └── styles.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── mipmap-anydpi-v33
│ │ │ └── ic_launcher.xml
│ │ ├── drawable
│ │ │ ├── ic_baseline_arrow_upward_24.xml
│ │ │ ├── ic_baseline_add_24.xml
│ │ │ ├── ic_baseline_arrow_downward_24.xml
│ │ │ ├── baseline_delete_24.xml
│ │ │ ├── baseline_web_24.xml
│ │ │ ├── ic_baseline_delete_24.xml
│ │ │ ├── ic_baseline_info_24.xml
│ │ │ ├── ic_baseline_clear_24.xml
│ │ │ ├── baseline_login_24.xml
│ │ │ ├── ic_baseline_cloud_download_24.xml
│ │ │ ├── baseline_cloud_24.xml
│ │ │ ├── ic_baseline_create_24.xml
│ │ │ ├── ic_baseline_more_horiz_24.xml
│ │ │ ├── baseline_cloud_download_24.xml
│ │ │ ├── ic_baseline_arrow_circle_down_24.xml
│ │ │ ├── ic_search_black_24dp.xml
│ │ │ ├── baseline_cloud_done_24.xml
│ │ │ ├── avatar.xml
│ │ │ ├── baseline_magent_moderator_24.xml
│ │ │ ├── baseline_video_moderator_24.xml
│ │ │ ├── ic_baseline_outlet_24.xml
│ │ │ ├── baseline_content_paste_24.xml
│ │ │ ├── android_exit.xml
│ │ │ ├── ic_baseline_select_reverse_24.xml
│ │ │ ├── ic_baseline_select_all_24.xml
│ │ │ ├── ic_baseline_text_24.xml
│ │ │ ├── txt.xml
│ │ │ ├── baseline_log_24.xml
│ │ │ ├── ic_baseline_face_24.xml
│ │ │ ├── ic_round_insert_drive_file_24.xml
│ │ │ ├── ic_baseline_content_cut_24.xml
│ │ │ ├── other.xml
│ │ │ ├── mp4.xml
│ │ │ ├── folder.xml
│ │ │ ├── ic_round_folder_24.xml
│ │ │ ├── mp3.xml
│ │ │ ├── png.xml
│ │ │ ├── iso.xml
│ │ │ ├── apk.xml
│ │ │ ├── zip.xml
│ │ │ ├── baseline_settings_24.xml
│ │ │ ├── torrent.xml
│ │ │ ├── exe.xml
│ │ │ └── ic_launcher_background.xml
│ │ ├── xml
│ │ │ ├── backup_rules.xml
│ │ │ ├── data_extraction_rules.xml
│ │ │ └── root_preferences.xml
│ │ ├── layout
│ │ │ └── activity_video.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ └── github
│ │ │ └── zerorooot
│ │ │ └── nap511
│ │ │ ├── ui
│ │ │ └── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Type.kt
│ │ │ │ └── Theme.kt
│ │ │ ├── factory
│ │ │ └── CookieViewModelFactory.kt
│ │ │ ├── service
│ │ │ ├── VideoService.kt
│ │ │ ├── OfflineService.kt
│ │ │ ├── Sha1Service.kt
│ │ │ └── FileService.kt
│ │ │ ├── util
│ │ │ ├── DialogSwitchUtil.kt
│ │ │ ├── MyRsaUtil.kt
│ │ │ ├── ConfigKeyUtil.kt
│ │ │ └── DataStoreUtil.kt
│ │ │ ├── screen
│ │ │ ├── RecycleScreen.kt
│ │ │ ├── DropdownMenu.kt
│ │ │ ├── PhotoScreen.kt
│ │ │ ├── OfflineDownloadScreen.kt
│ │ │ ├── OfflineFileScreen.kt
│ │ │ ├── LogScreen.kt
│ │ │ ├── AppTopBar.kt
│ │ │ └── SettingScreenNew.kt
│ │ │ ├── screenitem
│ │ │ ├── OfflineCellItem.kt
│ │ │ └── RecycleCellItem.kt
│ │ │ ├── activity
│ │ │ ├── VideoActivity.kt
│ │ │ └── TorrentTaskActivity.kt
│ │ │ └── viewmodel
│ │ │ ├── RecycleViewModel.kt
│ │ │ └── OfflineFileViewModel.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .gitignore
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── deploymentTargetDropDown.xml
├── migrations.xml
├── misc.xml
├── deploymentTargetSelector.xml
├── gradle.xml
└── inspectionProfiles
│ └── Project_Default.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── README.md
├── settings.gradle
├── .github
└── workflows
│ ├── release.yml
│ └── generate-apk-release.yml
├── gradle.properties
└── gradlew.bat
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /src/androidTest
3 | /src/test
4 | /release
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zerorooot/nap511/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .idea
6 | .idea/
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 | .cxx
12 | local.properties
13 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_upward_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_add_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_downward_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_delete_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_web_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_delete_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_info_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_clear_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_login_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_cloud_download_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_cloud_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_create_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_more_horiz_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_cloud_download_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_circle_down_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_cloud_done_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/factory/CookieViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.factory
2 |
3 | import android.app.Application
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.ViewModelProvider
6 |
7 | class CookieViewModelFactory(
8 | private val cookie: String,
9 | private val application: Application,
10 | ) : ViewModelProvider.Factory {
11 | override fun create(modelClass: Class): T {
12 | return modelClass.getDeclaredConstructor(String::class.java, Application::class.java)
13 | .newInstance(cookie, application) as T
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/avatar.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_magent_moderator_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_video_moderator_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_outlet_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #FF29B6F6
11 | #FF039BE5
12 | #FFBDBDBD
13 | #FF757575
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_content_paste_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/android_exit.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_video.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nap511
2 |
3 | 一个Android自用的115网盘客户端,用于[Jetpack Compose](https://developer.android.com/jetpack/compose)练手
4 |
5 | # 功能
6 |
7 | 没划掉的是目前已实现的,划掉的是计划中的
8 |
9 | 1. 登录(账号密码网页端登录、cookie登录、登出)
10 | 2. 网盘文件相关(剪切、删除、重命名、新建文件夹、多选、回收站、获取下载链接、搜索、~~小文本创建~~、在线解压)
11 | 3. 离线文件相关(离线列表、点击跳转到网盘文件夹、查看视频文件、删除、清空)
12 | 4. 查看(小文本、~~音频~~、照片、视频)
13 | 5. 离线(磁力链接离线下载、115sha1导出、种子离线下载)
14 | 6. 自定义(单次文件请求数量、默认离线位置)
15 | 7. 其他(磁力跳转、~~支持大屏~~)
16 |
17 | 不支持**文件的上传与下载**、**两步验证**、**安全密钥**
18 |
19 | # 下载
20 | ## 尝鲜版
21 |
22 | 需登录github账户:https://github.com/zerorooot/nap511/actions/workflows/generate-apk-release.yml
23 |
24 | 无需登录github账户:https://nightly.link/zerorooot/nap511/workflows/generate-apk-release/main?preview
25 |
26 | ## 稳定版
27 | https://github.com/zerorooot/nap511/releases
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_select_reverse_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_select_all_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_text_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/txt.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_log_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_face_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven { url 'https://maven.aliyun.com/repository/central' }
4 | maven { url 'https://maven.aliyun.com/repository/public' }
5 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
6 | google()
7 | mavenCentral()
8 | gradlePluginPortal()
9 | }
10 | }
11 | dependencyResolutionManagement {
12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
13 | repositories {
14 | maven { url 'https://jitpack.io' }
15 | maven{ url 'https://maven.aliyun.com/repository/central' }
16 | maven{ url 'https://maven.aliyun.com/repository/public' }
17 | maven{ url 'https://maven.aliyun.com/repository/gradle-plugin'}
18 | google()
19 | mavenCentral()
20 | }
21 | }
22 | rootProject.name = "nap511"
23 | include ':app'
24 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_round_insert_drive_file_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_content_cut_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/other.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/mp4.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/folder.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_round_folder_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/mp3.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/png.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/iso.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable/apk.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/zip.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/app/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
22 |
23 | #-dontwarn org.bouncycastle.jsse.BCSSLParameters
24 | #-dontwarn org.bouncycastle.jsse.BCSSLSocket
25 | #-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
26 | #-dontwarn org.conscrypt.Conscrypt$Version
27 | #-dontwarn org.conscrypt.Conscrypt
28 | #-dontwarn org.openjsse.javax.net.ssl.SSLParameters
29 | #-dontwarn org.openjsse.javax.net.ssl.SSLSocket
30 | #-dontwarn org.openjsse.net.ssl.OpenJSSE
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Release
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-java@v3
16 | with:
17 | distribution: temurin
18 | java-version: 17
19 | - uses: gradle/gradle-build-action@v2
20 | with:
21 | gradle-version: current
22 | arguments: assembleRelease
23 |
24 | - uses: ilharp/sign-android-release@v1
25 | id: sign_app
26 | with:
27 | releaseDir: app/build/outputs/apk/release
28 | signingKey: ${{ secrets.ANDROID_SIGNING_KEY }}
29 | keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }}
30 | keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
31 | keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
32 | buildToolsVersion: 35.0.0
33 |
34 | - run: mv ${{steps.sign_app.outputs.signedFile}} Nap511_$GITHUB_REF_NAME.apk
35 | - uses: ncipollo/release-action@v1
36 | with:
37 | artifacts: "*.apk"
38 | token: ${{ github.token }}
39 | generateReleaseNotes: true
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_settings_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/res/drawable/torrent.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
16 |
--------------------------------------------------------------------------------
/.github/workflows/generate-apk-release.yml:
--------------------------------------------------------------------------------
1 | name: automated build
2 |
3 | env:
4 | # The name of the main module repository
5 | main_project_module: app
6 |
7 | on:
8 | push
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | permissions:
14 | contents: write
15 | steps:
16 | - uses: actions/checkout@v3
17 | # Set Current Date As Env Variable
18 | - name: Set current date as env variable
19 | run: echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
20 |
21 | - uses: actions/setup-java@v3
22 | with:
23 | distribution: 'zulu' # See 'Supported distributions' for available options
24 | java-version: '17'
25 | cache: 'gradle'
26 |
27 | - uses: gradle/gradle-build-action@v2
28 | with:
29 | gradle-version: '8.9'
30 | arguments: assembleRelease
31 |
32 |
33 | - uses: ilharp/sign-android-release@v1
34 | id: sign_app
35 | with:
36 | releaseDir: app/build/outputs/apk/release
37 | signingKey: ${{ secrets.ANDROID_SIGNING_KEY }}
38 | keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }}
39 | keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
40 | keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
41 | buildToolsVersion: 35.0.0
42 |
43 |
44 | # Noted For Output [main_project_module]/build/outputs/apk/release/
45 | - name: Upload APK Release - ${{ env.repository_name }}
46 | uses: actions/upload-artifact@v4
47 | with:
48 | name: ${{ env.date_today }} - Nap511 release generated
49 | path: ${{steps.sign_app.outputs.signedFile}}
50 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/service/VideoService.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.service
2 |
3 | import github.zerorooot.nap511.bean.BaseReturnMessage
4 | import github.zerorooot.nap511.bean.VideoInfoBean
5 | import okhttp3.Interceptor
6 | import okhttp3.OkHttpClient
7 | import retrofit2.Retrofit
8 | import retrofit2.converter.gson.GsonConverterFactory
9 | import retrofit2.http.*
10 |
11 | interface VideoService {
12 | companion object {
13 | private var videoService: VideoService? = null
14 | fun getInstance(cookie: String): VideoService {
15 | if (videoService == null) {
16 | videoService = Retrofit
17 | .Builder()
18 | .baseUrl("https://115vod.com/webapi/files/")
19 | .addConverterFactory(GsonConverterFactory.create())
20 | //add cookie
21 | .client(OkHttpClient().newBuilder().addInterceptor(Interceptor { chain ->
22 | chain.proceed(
23 | chain.request().newBuilder().addHeader("Cookie", cookie).build()
24 | );
25 | }).build())
26 | .build()
27 | .create(VideoService::class.java)
28 | }
29 | return videoService!!
30 | }
31 | }
32 |
33 | @FormUrlEncoded
34 | @POST("history")
35 | /**
36 | builder.add("op", "update")
37 | builder.add("pick_code", intent.getStringExtra("pick_code")!!)
38 | builder.add("definition", "0")
39 | builder.add("category", "1")
40 | builder.add("share_id", "0")
41 | */
42 | suspend fun history(@FieldMap body: HashMap): BaseReturnMessage
43 |
44 | @GET("video")
45 | suspend fun videoInfo(
46 | @Query("pickcode") pickCode: String,
47 | @Query("share_id") shareId: String = "0"
48 | ): VideoInfoBean
49 |
50 |
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/util/DialogSwitchUtil.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.util
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 |
7 | /**
8 | * 打开对话框相关
9 | */
10 | class DialogSwitchUtil {
11 | companion object {
12 | @Volatile
13 | private var INSTANCE: DialogSwitchUtil? = null
14 | fun getInstance(): DialogSwitchUtil {
15 | return INSTANCE ?: synchronized(this) {
16 | INSTANCE ?: DialogSwitchUtil().also { INSTANCE = it }
17 | }
18 | }
19 | }
20 |
21 | /**
22 | * 创建选择种子文件对话框
23 | */
24 | var isOpenCreateSelectTorrentFileDialog by mutableStateOf(false)
25 |
26 | /**
27 | * 打开离线对话框
28 | */
29 | var isOpenOfflineDialog by mutableStateOf(false)
30 |
31 | /**
32 | * 垃圾站密码对话框
33 | */
34 | var isOpenRecyclePasswordDialog by mutableStateOf(false)
35 | /**
36 | * 新建文件夹
37 | */
38 | var isOpenCreateFolderDialog by mutableStateOf(false)
39 |
40 | /**
41 | * 重命名
42 | */
43 | var isOpenRenameFileDialog by mutableStateOf(false)
44 |
45 | /**
46 | *文件信息
47 | */
48 | var isOpenFileInfoDialog by mutableStateOf(false)
49 |
50 | /**
51 | *文件排序
52 | */
53 | var isOpenFileOrderDialog by mutableStateOf(false)
54 |
55 | /**
56 | *aria2
57 | */
58 | var isOpenAria2Dialog by mutableStateOf(false)
59 |
60 |
61 | /**
62 | *搜索
63 | */
64 | var isOpenSearchDialog by mutableStateOf(false)
65 |
66 | /**
67 | * 解压对话框
68 | */
69 | var isOpenUnzipDialog by mutableStateOf(false)
70 |
71 | /**
72 | *解压密码
73 | */
74 | var isOpenUnzipPasswordDialog by mutableStateOf(false)
75 |
76 | /**
77 | *小文本文件 [github.zerorooot.nap511.screen.TextBodyDialog]
78 | */
79 | var isOpenTextBodyDialog by mutableStateOf(false)
80 |
81 | /**
82 | *解压所有压缩包,提前输入密码,没有为空即可
83 | */
84 | var isOpenUnzipAllFileDialog by mutableStateOf(false)
85 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/exe.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | nap511
3 |
4 | - 文件排序
5 | - 刷新文件
6 |
7 |
8 | - 滚动顶部
9 | - 滚动底部
10 | - 清空日志
11 | - 导出日志
12 |
13 |
14 | - 刷新文件
15 | - 复制所有下载链接
16 | - 清空已完成
17 | - 清空已失败
18 |
19 |
20 | - 剪切
21 | - 删除
22 | - 重命名
23 | - 文件信息
24 | - 通过aria2下载
25 |
26 |
27 | - 还原
28 | - 删除
29 |
30 |
31 | - 复制下载链接
32 | - 删除文件
33 | - 文件信息
34 |
35 |
36 |
37 | - 文件名称⬆️
38 | - 文件名称⬇️
39 | - 文件大小⬆️
40 | - 文件大小⬇️
41 | - 更改时间⬆️
42 | - 更改时间⬇️
43 | - 文件种类⬆️
44 | - 文件种类⬇️
45 | - 视频时间
46 |
47 |
48 | - Start
49 | - Center
50 | - End
51 | - EndOverlay
52 |
53 |
54 | uid
55 | cookie
56 | password
57 | aria2Url
58 | http://127.0.0.1:6800/jsonrpc
59 | aria2Token
60 | requestLimitCount
61 | defaultOfflineCid
62 | floatingActionButtonPosition
63 | 日志页面
64 | autoRotate
65 | hideLoadingView
66 | EarlyLoading
67 | PositionAfterAt
68 | TorrentSort
69 | SaveRequestCache
70 | offlineMethod
71 | defaultOfflineTime
72 | defaultOfflineCount
73 | currentOfflineTask
74 | clickDownloadNow
75 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.DisposableEffect
13 | import androidx.compose.runtime.SideEffect
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.toArgb
16 | import androidx.compose.ui.platform.LocalContext
17 | import androidx.compose.ui.platform.LocalView
18 | import androidx.core.view.ViewCompat
19 | import com.google.accompanist.systemuicontroller.rememberSystemUiController
20 |
21 | private val DarkColorScheme = darkColorScheme(
22 | primary = Purple80,
23 | secondary = PurpleGrey80,
24 | tertiary = Pink80
25 | )
26 |
27 | private val LightColorScheme = lightColorScheme(
28 | primary = Purple40,
29 | secondary = PurpleGrey40,
30 | tertiary = Pink40
31 |
32 | /* Other default colors to override
33 | background = Color(0xFFFFFBFE),
34 | surface = Color(0xFFFFFBFE),
35 | onPrimary = Color.White,
36 | onSecondary = Color.White,
37 | onTertiary = Color.White,
38 | onBackground = Color(0xFF1C1B1F),
39 | onSurface = Color(0xFF1C1B1F),
40 | */
41 | )
42 |
43 | @Composable
44 | fun Nap511Theme(
45 | darkTheme: Boolean = isSystemInDarkTheme(),
46 | // Dynamic color is available on Android 12+
47 | dynamicColor: Boolean = true,
48 | content: @Composable () -> Unit
49 | ) {
50 | val colorScheme = when {
51 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
52 | val context = LocalContext.current
53 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
54 | }
55 | darkTheme -> DarkColorScheme
56 | else -> LightColorScheme
57 | }
58 | val view = LocalView.current
59 | if (!view.isInEditMode) {
60 | SideEffect {
61 | (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
62 | ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
63 | }
64 | }
65 |
66 | MaterialTheme(
67 | colorScheme = colorScheme,
68 | typography = Typography,
69 | content = content
70 | )
71 |
72 | // Remember a SystemUiController
73 | val systemUiController = rememberSystemUiController()
74 | val useDarkIcons = !darkTheme
75 | SideEffect {
76 | systemUiController.setSystemBarsColor(
77 | color = if (darkTheme) LightColorScheme.primary else DarkColorScheme.primary,
78 | darkIcons = useDarkIcons
79 | )
80 | systemUiController.setNavigationBarColor(
81 | color = Color.Transparent,
82 | darkIcons = useDarkIcons
83 | )
84 | }
85 |
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/util/MyRsaUtil.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.util
2 |
3 | import java.math.BigInteger
4 |
5 | class MyRsaUtil {
6 | private val n: BigInteger = BigInteger(
7 | "8686980c0f5a24c4b9d43020cd2c22703ff3f450756529058b1cf88f09b8602136477198a6e2683149659bd122c33592fdb5ad47944ad1ea4d36c6b172aad6338c3bb6ac6227502d010993ac967d1aef00f0c8e038de2e4d3bc2ec368af2e9f10a6f1eda4f7262f136420c07c331b871bf139f74f3010e3c4fe57df3afb71683",
8 | 16
9 | )
10 | private val e: BigInteger = BigInteger("10001", 16)
11 |
12 | fun encrypt(text: String): String {
13 | val m = pkcs1pad2(text, 0x80)
14 | val c = m.modPow(e, n)
15 | var h = c.toString(16)
16 | while (h.length < 0x80 * 2) {
17 | h = "0$h"
18 | }
19 | return h
20 | }
21 |
22 | fun decrypt(text: String): String {
23 | val ba = Array(text.length) { 0 }
24 | var i = 0
25 | while (i < text.length) {
26 | ba[i] = text.toCharArray()[i].code
27 | i++
28 | }
29 | val a = BigInteger(a2hex(ba), 16)
30 | val c = a.modPow(e, n)
31 | return pkcs1unpad2(c)
32 | }
33 |
34 | private fun pkcs1unpad2(a: BigInteger): String {
35 | var b = a.toString(16)
36 | if (b.length % 2 != 0) {
37 | b = "0$b"
38 | }
39 | val c = hex2a(b)
40 | var i = 1
41 | while (c.toCharArray()[i].code != 0) {
42 | i++
43 | }
44 | return c.slice(IntRange(i + 1, c.length - 1))
45 | }
46 |
47 |
48 | private fun pkcs1pad2(s: String, int: Int): BigInteger {
49 | var n = int
50 | var i = s.length - 1
51 | val ba = Array(n) { 0 }
52 | while (i >= 0 && n > 0) {
53 | ba[--n] = s.toCharArray()[i--].code
54 | }
55 | ba[--n] = 0
56 | while (n > 2) { // random non-zero pad
57 | ba[--n] = 0xff
58 | }
59 | ba[--n] = 2
60 | ba[--n] = 0
61 | val c = a2hex(ba)
62 | return BigInteger(c, 16)
63 | }
64 |
65 | private fun a2hex(byteArray: Array): String {
66 | var hexString = ""
67 | var nextHexByte: String
68 | byteArray.forEach { i ->
69 | nextHexByte = i.toString(16)
70 | if (nextHexByte.length < 2) {
71 | nextHexByte = "0$nextHexByte"
72 | }
73 | hexString += nextHexByte
74 | }
75 | return hexString
76 | }
77 |
78 | private fun hex2a(hex: String): String {
79 | var str = ""
80 | var i = 0
81 | while (i < hex.length) {
82 | val s = hex.substring(i, i + 2).toInt(16).toChar()
83 | str += s
84 | i += 2
85 | }
86 |
87 | return str
88 | }
89 |
90 | fun hex2aByteArray(hex: String): ByteArray {
91 | val str = arrayListOf()
92 | run {
93 | var i = 0
94 | while (i < hex.length) {
95 | val s = hex.substring(i, i + 2).toInt(16).toChar().code.toByte()
96 | str.add(s)
97 | i += 2
98 | }
99 | }
100 | return str.toByteArray()
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/RecycleScreen.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.lazy.LazyColumn
9 | import androidx.compose.foundation.lazy.itemsIndexed
10 | import androidx.compose.material.ExperimentalMaterialApi
11 | import androidx.compose.material.pullrefresh.PullRefreshIndicator
12 | import androidx.compose.material.pullrefresh.pullRefresh
13 | import androidx.compose.material.pullrefresh.rememberPullRefreshState
14 | import androidx.compose.runtime.*
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.unit.dp
18 | import github.zerorooot.nap511.screenitem.RecycleCellItem
19 | import github.zerorooot.nap511.util.App
20 | import github.zerorooot.nap511.util.ConfigKeyUtil
21 | import github.zerorooot.nap511.util.DataStoreUtil
22 | import github.zerorooot.nap511.viewmodel.RecycleViewModel
23 |
24 | @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
25 | @Composable
26 | fun RecycleScreen(recycleViewModel: RecycleViewModel) {
27 | recycleViewModel.getRecycleFileList()
28 | var deleteIndex by remember {
29 | mutableIntStateOf(-1)
30 | }
31 | val refreshing by recycleViewModel.isRefreshing.collectAsState()
32 | val recycleFileList = recycleViewModel.recycleFileList
33 |
34 | val menuOnClick = { name: String, index: Int ->
35 | when (name) {
36 | "还原" -> recycleViewModel.revert(index)
37 | "删除" -> {
38 | deleteIndex = index
39 | recycleViewModel.delete(index)
40 | }
41 | }
42 | }
43 |
44 | val appBarOnClick = { name: String ->
45 | when (name) {
46 | "清空所有文件" -> recycleViewModel.deleteAll()
47 | "ModalNavigationDrawerMenu" -> App.instance.openDrawerState()
48 | }
49 | }
50 |
51 | RecyclePasswordDialog {
52 | if (it != "") {
53 | DataStoreUtil.putData(ConfigKeyUtil.PASSWORD, it)
54 |
55 | if (deleteIndex == -1) {
56 | recycleViewModel.deleteAll()
57 | } else {
58 | recycleViewModel.delete(deleteIndex, it.subSequence(0, 6).toString(), true)
59 | deleteIndex = -1
60 | }
61 | }
62 | recycleViewModel.closeDialog()
63 | }
64 |
65 | val pullRefreshState = rememberPullRefreshState(refreshing, { recycleViewModel.refresh() })
66 |
67 | Column {
68 | AppTopBarRecycle("回收站", appBarOnClick)
69 | MiddleEllipsisText(
70 | text = "当前文件数:${recycleFileList.size}", modifier = Modifier.padding(8.dp, 4.dp)
71 | )
72 | Box(Modifier.pullRefresh(pullRefreshState)) {
73 | LazyColumn(Modifier.fillMaxSize()) {
74 | itemsIndexed(items = recycleFileList, key = { _, item ->
75 | item.hashCode()
76 | }) { index, item ->
77 | RecycleCellItem(
78 | recycleBean = item,
79 | Modifier.animateItemPlacement(),
80 | index = index,
81 | menuOnClick
82 | )
83 | }
84 | }
85 |
86 | PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
87 | }
88 |
89 | }
90 |
91 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/service/OfflineService.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.service
2 |
3 | import github.zerorooot.nap511.bean.*
4 | import okhttp3.Interceptor
5 | import okhttp3.OkHttpClient
6 | import retrofit2.Retrofit
7 | import retrofit2.converter.gson.GsonConverterFactory
8 | import retrofit2.http.*
9 |
10 | interface OfflineService {
11 | companion object {
12 | private var offlineService: OfflineService? = null
13 | fun getInstance(cookie: String): OfflineService {
14 | if (offlineService == null) {
15 | offlineService = Retrofit
16 | .Builder()
17 | .baseUrl("https://115.com")
18 | .addConverterFactory(GsonConverterFactory.create())
19 | // .addConverterFactory(FileBeanConverterFactory.create())
20 | //add cookie
21 | .client(OkHttpClient().newBuilder().addInterceptor(Interceptor { chain ->
22 | chain.proceed(
23 | chain.request().newBuilder().addHeader("Cookie", cookie).build()
24 | );
25 | }).build())
26 | .build()
27 | .create(OfflineService::class.java)
28 | }
29 | return offlineService!!
30 | }
31 | }
32 |
33 | @FormUrlEncoded
34 | @POST("web/lixian/?ct=lixian&ac=add_task_urls")
35 | suspend fun addTask(@FieldMap body: HashMap): BaseReturnMessage
36 |
37 | @GET("web/lixian/?ct=lixian&ac=get_quota_package_info")
38 | suspend fun quota(): QuotaBean
39 |
40 | @GET("?ct=offline&ac=space")
41 | suspend fun getSign(@Query("_") currentTime: Long = System.currentTimeMillis() / 1000): SignBean
42 |
43 | /**
44 | *
45 | */
46 | @FormUrlEncoded
47 | @POST("web/lixian/?ct=lixian&ac=task_lists")
48 | suspend fun taskList(
49 | @Field("uid") uid: String = "",
50 | @Field("sign") sign: String = "",
51 | @Field("page") page: Int = 1,
52 | @Field("time") time: Long = System.currentTimeMillis() / 1000
53 | ): OfflineInfo
54 |
55 | @FormUrlEncoded
56 | @POST("web/lixian/?ct=lixian&ac=torrent")
57 | suspend fun getTorrentTaskList(
58 | @Field("sha1") sha1: String = "",
59 | @Field("sign") sign: String = "",
60 | @Field("uid") uid: String = "",
61 | @Field("time") time: Long = System.currentTimeMillis() / 1000
62 | ): TorrentFileBean
63 |
64 | @FormUrlEncoded
65 | @POST("web/lixian/?ct=lixian&ac=add_task_bt")
66 | suspend fun addTorrentTask(
67 | @Field("info_hash") infoHash: String = "",
68 | //0,4,6
69 | @Field("wanted") wanted: String = "",
70 | //torrent name
71 | @Field("savepath") savePath: String = "",
72 | @Field("uid") uid: String = "",
73 | @Field("sign") sign: String = "",
74 | @Field("time") time: Long = System.currentTimeMillis() / 1000
75 | ): BaseReturnMessage
76 |
77 |
78 | /**
79 | * hash[0]:xxxxxxx
80 | * uid
81 | * sign
82 | * time
83 | */
84 | @FormUrlEncoded
85 | @POST("web/lixian/?ct=lixian&ac=task_del")
86 | suspend fun deleteTask(
87 | @FieldMap deleteHash: HashMap,
88 | ): BaseReturnMessage
89 |
90 | @FormUrlEncoded
91 | @POST("web/lixian/?ct=lixian&ac=task_clear")
92 | suspend fun clearFinish(@Field("flag") flag: String = "0"): BaseReturnMessage
93 |
94 | @FormUrlEncoded
95 | @POST("web/lixian/?ct=lixian&ac=task_clear")
96 | suspend fun clearError(@Field("flag") flag: String = "2"): BaseReturnMessage
97 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
15 |
16 |
17 |
21 |
22 |
27 |
28 |
33 |
34 |
39 |
40 |
46 |
47 |
54 |
55 |
56 |
60 |
61 |
65 |
66 |
71 |
72 |
77 |
78 |
83 |
84 |
89 |
90 |
95 |
96 |
101 |
102 |
107 |
108 |
109 |
114 |
115 |
119 |
120 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screenitem/OfflineCellItem.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screenitem
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.combinedClickable
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material3.*
8 | import androidx.compose.runtime.*
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.layout.ContentScale
12 | import androidx.compose.ui.res.painterResource
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import androidx.compose.ui.unit.*
15 | import github.zerorooot.nap511.R
16 | import github.zerorooot.nap511.bean.OfflineTask
17 | import github.zerorooot.nap511.screen.OfflineFileMoreMenu
18 |
19 |
20 | @OptIn(ExperimentalFoundationApi::class)
21 | @Composable
22 | fun OfflineCellItem(
23 | offlineTask: OfflineTask,
24 | index: Int,
25 | itemOnClick: (Int) -> Unit,
26 | menuOnClick: (String, Int) -> Unit
27 | ) {
28 | val image = if (offlineTask.fileId == "") R.drawable.other else R.drawable.folder
29 | val name = offlineTask.name
30 | val size = offlineTask.sizeString
31 | val time = offlineTask.timeString
32 | val percent = offlineTask.percentString
33 | Surface(
34 | shape = MaterialTheme.shapes.medium,
35 | tonalElevation = 10.dp,
36 | modifier = Modifier
37 | .padding(1.dp)
38 | .combinedClickable(
39 | onClick = {
40 | itemOnClick.invoke(index)
41 | }
42 | ),
43 | ) {
44 | Card(
45 | modifier = Modifier
46 | .padding(4.dp, 4.dp)
47 | .height(80.dp),
48 | ) {
49 | Row(
50 | Modifier
51 | .fillMaxSize()
52 | ) {
53 | Box(
54 | Modifier
55 | .height(60.dp)
56 | .align(Alignment.CenterVertically)
57 | ) {
58 | Image(
59 | painter = painterResource(image),
60 | modifier = Modifier
61 | .height(60.dp)
62 | .width(60.dp),
63 | contentScale = ContentScale.Fit,
64 | contentDescription = "",
65 | )
66 | }
67 |
68 | Column(
69 | verticalArrangement = Arrangement.SpaceEvenly,
70 | modifier = Modifier
71 | .fillMaxHeight()
72 | .weight(0.7f)
73 | ) {
74 | AutoSizableTextField(
75 | value = name,
76 | modifier = Modifier
77 | .padding(start = 4.dp, top = 9.dp)
78 | .fillMaxWidth(),
79 | minFontSize = 10.sp,
80 | maxLines = 2
81 | )
82 | Row(
83 | verticalAlignment = Alignment.CenterVertically,
84 | modifier = Modifier
85 | .padding(start = 5.dp, top = 9.dp)
86 | .fillMaxSize()
87 | ) {
88 | Text(
89 | text = size,
90 | style = MaterialTheme.typography.bodyMedium,
91 | )
92 | Text(
93 | text = percent,
94 | style = MaterialTheme.typography.bodyMedium,
95 | modifier = Modifier.padding(start = 8.dp)
96 | )
97 | Text(
98 | text = time,
99 | style = MaterialTheme.typography.bodyMedium,
100 | modifier = Modifier.padding(start = 8.dp)
101 | )
102 | }
103 | }
104 |
105 | OfflineFileMoreMenu() { itemName, _ ->
106 | menuOnClick.invoke(itemName, index)
107 | }
108 | }
109 |
110 | }
111 |
112 |
113 | }
114 | }
115 |
116 |
117 | @Preview
118 | @Composable
119 | fun p() {
120 | val offlineTask = OfflineTask(
121 | name = "test file",
122 | sizeString = "417.26G",
123 | percentString = "43%",
124 | timeString = "2023-02-13 12:43"
125 | )
126 | OfflineCellItem(offlineTask, 1, {}, { _, _ -> })
127 |
128 | }
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/DropdownMenu.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxHeight
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.wrapContentSize
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.MoreVert
9 | import androidx.compose.material3.*
10 | import androidx.compose.runtime.*
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.res.stringArrayResource
15 | import github.zerorooot.nap511.R
16 |
17 | @Composable
18 | private fun MyDropdownMenu(
19 | listItems: List,
20 | modifier: Modifier,
21 | icon: @Composable () -> Unit,
22 | onClick: (String, Int) -> Unit
23 | ) {
24 | // val disabledItem = 2
25 | // val contextForToast = LocalContext.current.applicationContext
26 |
27 | // state of the menu
28 | var expanded by remember {
29 | mutableStateOf(false)
30 | }
31 |
32 | Box(
33 | // contentAlignment = Alignment.Center,
34 | // modifier = Modifier.fillMaxHeight()
35 | modifier = modifier
36 | // Modifier.wrapContentSize(Alignment.TopEnd)
37 | ) {
38 | IconButton(
39 | content = icon,
40 | onClick = {
41 | expanded = true
42 | }
43 | )
44 | // drop down menu
45 | DropdownMenu(
46 | expanded = expanded,
47 | onDismissRequest = {
48 | expanded = false
49 | }
50 | ) {
51 | // adding items
52 | listItems.forEachIndexed { itemIndex, itemValue ->
53 | DropdownMenuItem(
54 | onClick = {
55 | onClick.invoke(itemValue, itemIndex)
56 | expanded = false
57 | },
58 | // enabled = (itemIndex == disabledItem),
59 | text = { Text(text = itemValue) }
60 | )
61 | }
62 | }
63 | }
64 | }
65 |
66 | @Composable
67 | fun FileMoreMenu(onClick: (String, Int) -> Unit) {
68 | val listOf = stringArrayResource(id = R.array.fileMenu).toList()
69 | BaseMoreMenu(listOf, onClick)
70 | }
71 |
72 | @Composable
73 | fun RecycleMoreMenu(onClick: (String, Int) -> Unit) {
74 | val listOf = stringArrayResource(id = R.array.recycleMenu).toList()
75 | BaseMoreMenu(listOf, onClick)
76 | }
77 |
78 | @Composable
79 | fun OfflineFileMoreMenu(onClick: (String, Int) -> Unit) {
80 | val listOf = stringArrayResource(id = R.array.offlineFileMenu).toList()
81 | BaseMoreMenu(listOf, onClick)
82 | }
83 |
84 | @Composable
85 | fun BaseMoreMenu(listOf: List, onClick: (String, Int) -> Unit) {
86 | MyDropdownMenu(
87 | listOf,
88 | Modifier
89 | .fillMaxHeight()
90 | .wrapContentSize(Alignment.Center),
91 | {
92 | Icon(
93 | imageVector = Icons.Default.MoreVert,
94 | contentDescription = "Open Options",
95 | tint = Color(0xFF85BCFF),
96 | modifier = Modifier
97 | .fillMaxSize()
98 | )
99 | }, onClick
100 | )
101 | }
102 |
103 | @Composable
104 | fun FileAppTopBarDropdownMenu(onClick: (String, Int) -> Unit) {
105 | val listOf = stringArrayResource(id = R.array.appBarMenu).toList()
106 | // val listOf = listOf("按文件名称排序", "按创建时间排序", "按修改时间排序","刷新")
107 | BaseAppTorBarMenu(listOf = listOf, onClick = onClick)
108 | }
109 |
110 | @Composable
111 | fun OfflineFileAppTopBarDropdownMenu(onClick: (String, Int) -> Unit) {
112 | val listOf = stringArrayResource(id = R.array.offlineFileAppBarMenu).toList()
113 | BaseAppTorBarMenu(listOf = listOf, onClick = onClick)
114 | }
115 | @Composable
116 | fun LogScreenTopBarDropdownMenu(onClick: (String, Int) -> Unit) {
117 | val listOf = stringArrayResource(id = R.array.logScreenAppBarMenu).toList()
118 | // val listOf = listOf("滚动顶部", "滚动底部", "清空日志","导出日志")
119 | BaseAppTorBarMenu(listOf = listOf, onClick = onClick)
120 | }
121 | @Composable
122 | fun RecycleAppTopBarDropdownMenu(onClick: (String, Int) -> Unit) {
123 | val listOf = listOf("清空所有文件")
124 | BaseAppTorBarMenu(listOf = listOf, onClick = onClick)
125 | }
126 |
127 | @Composable
128 | private fun BaseAppTorBarMenu(listOf: List, onClick: (String, Int) -> Unit) {
129 | MyDropdownMenu(
130 | listOf,
131 | Modifier.wrapContentSize(Alignment.TopEnd),
132 | {
133 | Icon(
134 | imageVector = Icons.Default.MoreVert,
135 | contentDescription = "Open Options"
136 | )
137 | }, onClick
138 | )
139 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/activity/VideoActivity.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.activity
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import android.widget.Toast
6 | import androidx.activity.addCallback
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.lifecycle.lifecycleScope
9 | import com.elvishew.xlog.XLog
10 | import com.shuyu.gsyvideoplayer.GSYVideoManager
11 | import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
12 | import com.shuyu.gsyvideoplayer.player.PlayerFactory
13 | import com.shuyu.gsyvideoplayer.utils.OrientationUtils
14 | import github.zerorooot.nap511.R
15 | import github.zerorooot.nap511.service.VideoService
16 | import github.zerorooot.nap511.util.ConfigKeyUtil
17 | import github.zerorooot.nap511.util.DataStoreUtil
18 | import kotlinx.coroutines.launch
19 | import okhttp3.FormBody
20 | import okhttp3.OkHttpClient
21 | import okhttp3.Request
22 | import okhttp3.RequestBody.Companion.toRequestBody
23 | import tv.danmaku.ijk.media.exo2.Exo2PlayerManager
24 | import kotlin.concurrent.thread
25 |
26 |
27 | class VideoActivity : AppCompatActivity() {
28 | private var orientationUtils: OrientationUtils? = null
29 | private lateinit var videoPlayer: MyGSYVideoPlayer
30 | private lateinit var videoService: VideoService
31 | private lateinit var cookie: String
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setContentView(R.layout.activity_video)
35 | val pickCode = intent.getStringExtra("pick_code")!!
36 | val address = "http://115.com/api/video/m3u8/${pickCode}.m3u8"
37 | val title = intent.getStringExtra("title")
38 | cookie = intent.getStringExtra("cookie")!!
39 | videoService = VideoService.getInstance(cookie)
40 |
41 | videoPlayer = findViewById(R.id.pre_video_player)
42 |
43 | PlayerFactory.setPlayManager(Exo2PlayerManager::class.java)
44 |
45 | //设置旋转
46 | orientationUtils = OrientationUtils(this, videoPlayer)
47 |
48 |
49 | videoPlayer.setUp(address, false, null, mapOf("Cookie" to cookie), title)
50 |
51 | //增加title
52 | videoPlayer.titleTextView.visibility = View.VISIBLE
53 | videoPlayer.titleTextView.isSelected = true
54 | videoPlayer.seekRatio = 10f
55 |
56 | //设置返回键
57 | videoPlayer.backButton.visibility = View.VISIBLE
58 | videoPlayer.setOrientationUtils(orientationUtils)
59 |
60 |
61 | videoPlayer.fullscreenButton.setOnClickListener {
62 | orientationUtils!!.resolveByClick()
63 | }
64 |
65 |
66 | //设置返回按键功能
67 | videoPlayer.backButton.setOnClickListener {
68 | back()
69 | }
70 |
71 | videoPlayer.startPlayLogic()
72 |
73 | if (DataStoreUtil.getData(ConfigKeyUtil.AUTO_ROTATE, false)) {
74 | //设置竖屏
75 | lifecycleScope.launch {
76 | val videoInfo = videoService.videoInfo(pickCode)
77 | val videoHeight = videoInfo.height
78 | val videoWidth = videoInfo.width
79 | if (videoHeight > videoWidth) {
80 | orientationUtils!!.resolveByClick()
81 | }
82 | }
83 | }
84 |
85 |
86 | videoPlayer.setVideoAllCallBack(object : GSYSampleCallBack() {
87 | override fun onPlayError(url: String?, vararg objects: Any?) {
88 | super.onPlayError(url, objects)
89 | Toast.makeText(baseContext, "播放失败~", Toast.LENGTH_SHORT).show()
90 | finish()
91 | }
92 | })
93 |
94 | onBackPressedDispatcher.addCallback(this) {
95 | back()
96 | }
97 | }
98 |
99 | override fun onPause() {
100 | super.onPause()
101 | videoPlayer.onVideoPause()
102 | }
103 |
104 | override fun onResume() {
105 | super.onResume()
106 | videoPlayer.onVideoResume()
107 | }
108 |
109 | override fun onDestroy() {
110 | super.onDestroy()
111 | updateTime()
112 | GSYVideoManager.releaseAllVideos()
113 | if (orientationUtils != null) orientationUtils!!.releaseListener()
114 | }
115 |
116 | private fun updateTime() {
117 | lifecycleScope.launch {
118 | val map = hashMapOf()
119 | map["op"] = "update"
120 | map["pick_code"] = intent.getStringExtra("pick_code")!!
121 | map["definition"] = "0"
122 | map["category"] = "1"
123 | map["share_id"] = "0"
124 | map["time"] = (videoPlayer.currentPositionWhenPlaying / 1000).toString()
125 | try {
126 | videoService.history(map)
127 | } catch (e: Exception) {
128 | e.printStackTrace()
129 | }
130 |
131 | }
132 | }
133 |
134 | private fun back() {
135 | updateTime()
136 | //释放所有
137 | videoPlayer.setVideoAllCallBack(null);
138 | finish()
139 | }
140 |
141 |
142 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screenitem/RecycleCellItem.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screenitem
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.material3.Card
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.Surface
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.layout.ContentScale
13 | import androidx.compose.ui.platform.LocalContext
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.text.style.TextOverflow
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.unit.sp
18 | import coil.compose.rememberAsyncImagePainter
19 | import coil.request.CachePolicy
20 | import coil.request.ImageRequest
21 | import coil.size.Scale
22 | import github.zerorooot.nap511.bean.RecycleBean
23 | import github.zerorooot.nap511.screen.RecycleMoreMenu
24 |
25 | @Composable
26 | fun RecycleCellItem(
27 | recycleBean: RecycleBean, //删除会有动画
28 | modifier: Modifier,
29 | index: Int,
30 | menuOnClick: (String, Int) -> Unit
31 | ) {
32 | val image = recycleBean.fileIco
33 | val name = recycleBean.fileName
34 | val size = recycleBean.fileSizeString
35 | val time = recycleBean.modifiedTimeString
36 | val parentName = recycleBean.parentName
37 | Surface(
38 | shape = MaterialTheme.shapes.medium,
39 | tonalElevation = 10.dp,
40 | modifier = modifier.padding(1.dp)
41 | ) {
42 | Card(
43 | modifier = Modifier
44 | .padding(4.dp, 4.dp)
45 | .height(85.dp),
46 | ) {
47 | Row(
48 | Modifier.fillMaxSize()
49 | ) {
50 | Box(
51 | Modifier
52 | .height(60.dp)
53 | .align(Alignment.CenterVertically)
54 | ) {
55 | Image(
56 | painter = if (recycleBean.photoThumb == "") painterResource(image) else (rememberAsyncImagePainter(
57 | ImageRequest.Builder(LocalContext.current)
58 | .data(data = recycleBean.photoThumb)
59 | .memoryCachePolicy(CachePolicy.ENABLED)
60 | .diskCachePolicy(CachePolicy.ENABLED)
61 | .networkCachePolicy(CachePolicy.ENABLED)
62 | .apply(block = fun ImageRequest.Builder.() {
63 | scale(Scale.FILL)
64 | placeholder(image)
65 | }).build()
66 | )),
67 | modifier = Modifier
68 | .height(60.dp)
69 | .width(60.dp),
70 | contentScale = ContentScale.Fit,
71 | contentDescription = "",
72 | )
73 | }
74 |
75 | Column(
76 | verticalArrangement = Arrangement.SpaceEvenly,
77 | modifier = Modifier
78 | .fillMaxHeight()
79 | .weight(0.7f)
80 | ) {
81 | AutoSizableTextField(
82 | value = name,
83 | modifier = Modifier
84 | .padding(start = 4.dp, top = 9.dp)
85 | .fillMaxWidth(),
86 | minFontSize = 10.sp,
87 | maxLines = 2
88 | )
89 | Text(
90 | modifier = Modifier.padding(start = 4.dp, top = 4.dp),
91 | text = parentName,
92 | overflow = TextOverflow.Ellipsis,
93 | style = MaterialTheme.typography.bodySmall,
94 | maxLines = 1
95 | )
96 | Row(
97 | verticalAlignment = Alignment.CenterVertically,
98 | modifier = Modifier
99 | .padding(start = 5.dp)
100 | .fillMaxSize()
101 | ) {
102 | Text(
103 | text = size,
104 | style = MaterialTheme.typography.bodyMedium,
105 | )
106 | Text(
107 | text = time,
108 | style = MaterialTheme.typography.bodyMedium,
109 | modifier = Modifier.weight(0.7f)
110 |
111 | )
112 |
113 | }
114 | }
115 |
116 | RecycleMoreMenu() { itemName, _ ->
117 | menuOnClick.invoke(itemName, index)
118 | }
119 |
120 | }
121 |
122 | }
123 |
124 |
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/util/ConfigKeyUtil.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.util
2 |
3 | import github.zerorooot.nap511.R
4 |
5 |
6 | /**
7 | * DataStoreUtil中的key
8 | */
9 | class ConfigKeyUtil {
10 | companion object {
11 | /**
12 | * aria2秘钥
13 | */
14 | val ARIA2_TOKEN by lazy { App.instance.getStringRes(R.string.ARIA2_TOKEN) }
15 |
16 | /**
17 | * aria2 url地址
18 | */
19 | val ARIA2_URL by lazy { App.instance.getStringRes(R.string.ARIA2_URL) }
20 |
21 | /**
22 | * aria2默认地址
23 | */
24 | val ARIA2_URL_DEFAULT_VALUE by lazy { App.instance.getStringRes(R.string.ARIA2_URL_DEFAULT_VALUE) }
25 |
26 | /**
27 | * cookie
28 | */
29 | val COOKIE by lazy { App.instance.getStringRes(R.string.COOKIE) }
30 |
31 | /**
32 | * user id
33 | */
34 | val UID by lazy { App.instance.getStringRes(R.string.UID) }
35 |
36 | /**
37 | * 回收站密码
38 | */
39 | val PASSWORD by lazy { App.instance.getStringRes(R.string.PASSWORD) }
40 |
41 | /**
42 | * sha1 service中使用,原本有发送到aria2和获取文件sha1两种,但现sha1废了,仅有发送到aria2
43 | */
44 | const val COMMAND = "command"
45 |
46 | /**
47 | * 发送到aria2
48 | */
49 | const val SENT_TO_ARIA2 = "sentToAria2"
50 |
51 | /**
52 | * 在设置中,视频是否自动旋转
53 | */
54 | val AUTO_ROTATE by lazy { App.instance.getStringRes(R.string.AUTO_ROTATE) }
55 |
56 |
57 | /**
58 | * 头像信息bean的json信息,包含头像url、过期时间、用户名、过期时间等
59 | * @see github.zerorooot.nap511.bean.AvatarBean
60 | */
61 | const val AVATAR_BEAN = "AVATAR_BEAN"
62 |
63 | /**
64 | * 默认离线位置
65 | */
66 | val DEFAULT_OFFLINE_CID by lazy { App.instance.getStringRes(R.string.DEFAULT_OFFLINE_CID) }
67 |
68 | /**
69 | * 默认请求个数,默认为100,具体在设置中设置
70 | */
71 | val REQUEST_LIMIT_COUNT by lazy { App.instance.getStringRes(R.string.REQUEST_LIMIT_COUNT) }
72 |
73 | /**
74 | * 默认离线个数,默认为5,具体在设置中设置
75 | */
76 | val DEFAULT_OFFLINE_COUNT by lazy { App.instance.getStringRes(R.string.DEFAULT_OFFLINE_COUNT) }
77 |
78 |
79 | /**
80 | * 当前缓存的离线任务
81 | */
82 | val CURRENT_OFFLINE_TASK by lazy { App.instance.getStringRes(R.string.CURRENT_OFFLINE_TASK) }
83 |
84 |
85 | /**
86 | * 离线任务缓存方式,true为x分钟后统一下载,false为集满后统一下载,具体在SettingActivity中设置
87 | */
88 | val OFFLINE_METHOD by lazy { App.instance.getStringRes(R.string.OFFLINE_METHOD) }
89 |
90 |
91 | /**
92 | * 默认离线时间,默认为5分钟,具体在设置中设置
93 | */
94 | val DEFAULT_OFFLINE_TIME by lazy { App.instance.getStringRes(R.string.DEFAULT_OFFLINE_TIME) }
95 |
96 |
97 | /**
98 | * 当视频正在加载时,隐藏loadingView
99 | */
100 | val HIDE_LOADING_VIEW by lazy { App.instance.getStringRes(R.string.HIDE_LOADING_VIEW) }
101 |
102 |
103 | /**
104 | * 提前加载上下两个文件夹,具体在设置中设置
105 | */
106 | val EARLY_LOADING by lazy { App.instance.getStringRes(R.string.EARLY_LOADING) }
107 |
108 | /**
109 | *重命名时,光标定位在@后
110 | */
111 | val POSITION_AFTER_AT by lazy { App.instance.getStringRes(R.string.POSITION_AFTER_AT) }
112 |
113 | /**
114 | * 保存请求缓存
115 | */
116 | val SAVE_REQUEST_CACHE by lazy { App.instance.getStringRes(R.string.SAVE_REQUEST_CACHE) }
117 |
118 | /**
119 | * 种子文件按文件大小从大到小排序
120 | */
121 | val TORRENT_SORT by lazy { App.instance.getStringRes(R.string.TORRENT_SORT) }
122 |
123 |
124 |
125 | /**
126 | * 浮动按钮位置
127 | */
128 | val FLOATING_ACTION_BUTTON_POSITION by lazy { App.instance.getStringRes(R.string.FLOATING_ACTION_BUTTON_POSITION) }
129 |
130 | /**
131 | * 磁力链接验证
132 | */
133 | const val VERIFY_MAGNET_LINK_ACCOUNT = "磁力链接验证"
134 |
135 | /**
136 | * 视频播放验证
137 | */
138 | const val VERIFY_VIDEO_ACCOUNT = "视频播放验证"
139 |
140 | /**
141 | * 登录
142 | */
143 | const val LOGIN = "登录"
144 |
145 | /**
146 | * 我的文件
147 | */
148 | const val MY_FILE = "我的文件"
149 |
150 | /**
151 | * 离线下载
152 | */
153 | const val OFFLINE_DOWNLOAD = "离线下载"
154 |
155 | /**
156 | * 离线列表
157 | */
158 | const val OFFLINE_LIST = "离线列表"
159 |
160 | /**
161 | * 网页版
162 | */
163 | const val WEB = "网页版"
164 |
165 | /**
166 | * 回收站
167 | */
168 | const val RECYCLE_BIN = "回收站"
169 |
170 | /**
171 | * 高级设置
172 | */
173 | const val ADVANCED_SETTINGS = "高级设置"
174 |
175 | /**
176 | * 退出应用
177 | */
178 | const val EXIT_APPLICATION = "退出应用"
179 |
180 | /**
181 | * 照片模式
182 | */
183 | const val PHOTO = "照片模式"
184 |
185 | /**
186 | * 日志页面
187 | */
188 | val LOG_SCREEN by lazy { App.instance.getStringRes(R.string.LOG_SCREEN) }
189 |
190 | /**
191 | * 立刻下载
192 | */
193 | val CLICK_DOWNLOAD_NOW by lazy { App.instance.getStringRes(R.string.CLICK_DOWNLOAD_NOW) }
194 | /**
195 | * 离线task的标签
196 | */
197 | const val OFFLINE_TASK_WORKER = "OfflineTaskWorker"
198 | }
199 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/PhotoScreen.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material.Surface
9 | import androidx.compose.material.Text
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.layout.ContentScale
16 | import androidx.compose.ui.platform.LocalContext
17 | import androidx.compose.ui.text.style.TextAlign
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.IntSize
20 | import androidx.compose.ui.unit.dp
21 | import coil.compose.rememberAsyncImagePainter
22 | import coil.request.CachePolicy
23 | import coil.request.ImageRequest
24 | import coil.size.Scale
25 | import com.google.accompanist.pager.ExperimentalPagerApi
26 | import com.google.accompanist.pager.HorizontalPager
27 | import com.google.accompanist.pager.HorizontalPagerIndicator
28 | import com.google.accompanist.pager.rememberPagerState
29 | import com.google.accompanist.systemuicontroller.rememberSystemUiController
30 | import com.smarttoolfactory.zoom.enhancedZoom
31 | import com.smarttoolfactory.zoom.rememberEnhancedZoomState
32 | import github.zerorooot.nap511.R
33 | import github.zerorooot.nap511.bean.ImageBean
34 | import github.zerorooot.nap511.ui.theme.Purple80
35 | import github.zerorooot.nap511.viewmodel.FileViewModel
36 |
37 | //todo image cache; image loading animation
38 | @Composable
39 | fun MyPhotoScreen(
40 | fileViewModel: FileViewModel,
41 | ) {
42 | fileViewModel.getImage(fileViewModel.photoFileBeanList, fileViewModel.photoIndexOf)
43 | val imageBeanList = fileViewModel.imageBeanList
44 | ImageBrowserScreen(imageBeanList, fileViewModel.photoIndexOf)
45 | rememberSystemUiController().apply {
46 | isSystemBarsVisible = false
47 | }
48 |
49 | }
50 |
51 | /**
52 | * 大图预览
53 | */
54 | @OptIn(ExperimentalPagerApi::class)
55 | @Composable
56 | private fun ImageBrowserScreen(images: List, currentIndex: Int = 0) {
57 | /**
58 | * 界面状态变更
59 | */
60 | val pageState = rememberPagerState(initialPage = currentIndex)
61 |
62 | Box {
63 | HorizontalPager(
64 | count = images.size,
65 | state = pageState,
66 | contentPadding = PaddingValues(horizontal = 0.dp),
67 | modifier = Modifier
68 | .fillMaxSize()
69 | ) { page ->
70 | FullScreenImage(images[page])
71 | }
72 |
73 |
74 | HorizontalPagerIndicator(
75 | pagerState = pageState,
76 | activeColor = Color.White,
77 | inactiveColor = Purple80,
78 | modifier = Modifier
79 | .align(Alignment.BottomCenter)
80 | .padding(bottom = 60.dp)
81 | )
82 | }
83 | }
84 |
85 | /**
86 | * https://developer.android.com/jetpack/compose/gestures?hl=zh-cn
87 | */
88 | @Composable
89 | private fun FullScreenImage(image: ImageBean) {
90 | Surface(
91 | modifier = Modifier
92 | .fillMaxSize(),
93 | color = Color.Black,
94 | ) {
95 | Text(
96 | text = image.fileName,
97 | textAlign = TextAlign.Center,
98 | color = Color.White,
99 | style = MaterialTheme.typography.titleMedium
100 | )
101 | Image(
102 | painter = rememberAsyncImagePainter(
103 | ImageRequest.Builder(LocalContext.current)
104 | .data(data = image.url)
105 | .apply(block = fun ImageRequest.Builder.() {
106 | scale(Scale.FILL)
107 | placeholder(R.drawable.png)
108 | })
109 | .memoryCachePolicy(CachePolicy.ENABLED)
110 | .diskCachePolicy(CachePolicy.ENABLED)
111 | .networkCachePolicy(CachePolicy.ENABLED)
112 | .build()
113 | ),
114 | contentDescription = "",
115 | contentScale = ContentScale.Fit,
116 | modifier = Modifier.enhancedZoom(
117 | clip = true,
118 | enhancedZoomState = rememberEnhancedZoomState(
119 | minZoom = .5f,
120 | imageSize = IntSize(1080, 1920),
121 | limitPan = true,
122 | moveToBounds = true
123 | ),
124 | enabled = { zoom, _, _ ->
125 | (zoom > 1f)
126 | }
127 | ),
128 |
129 | )
130 |
131 | }
132 | }
133 |
134 | /**
135 | * https://blog.csdn.net/sinat_38184748/article/details/121858073
136 | * 全屏图片查看
137 | */
138 |
139 |
140 | @Preview
141 | @Composable
142 | fun ImageBrowserScreenPreview() {
143 | val url =
144 | "http://192.168.123.154/hdd/tele/%e5%b0%81%e7%96%86%e7%96%86v%20-%20%e9%9f%b6%e5%8d%8e%e6%97%97%e8%a2%8d%e7%96%86%20%5b37P-376M%5d/"
145 | val images = mutableListOf()
146 | for (i in 10..15) {
147 | images.add(ImageBean("$url$i.jpg", "$i.jpg"))
148 | }
149 | ImageBrowserScreen(
150 | images,
151 | // selectImage = ImageBean(url = "http://192.168.123.159/hdd/tele/%e5%b0%81%e7%96%86%e7%96%86v%20-%20%e9%9f%b6%e5%8d%8e%e6%97%97%e8%a2%8d%e7%96%86%20%5b37P-376M%5d/01.jpg")
152 | )
153 | }
154 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/OfflineDownloadScreen.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 |
4 | import androidx.compose.foundation.layout.*
5 | //noinspection UsingMaterialAndMaterial3Libraries
6 | import androidx.compose.material.Surface
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.rounded.Menu
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.ExperimentalMaterial3Api
11 | import androidx.compose.material3.OutlinedTextField
12 | import androidx.compose.material3.Text
13 | import androidx.compose.material3.TopAppBar
14 | import androidx.compose.material3.TopAppBarDefaults
15 | import androidx.compose.runtime.*
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.platform.LocalConfiguration
19 | import androidx.compose.ui.res.stringResource
20 | import androidx.compose.ui.unit.dp
21 | import github.zerorooot.nap511.R
22 | import github.zerorooot.nap511.ui.theme.Purple80
23 | import github.zerorooot.nap511.util.App
24 | import github.zerorooot.nap511.viewmodel.FileViewModel
25 | import github.zerorooot.nap511.viewmodel.OfflineFileViewModel
26 |
27 |
28 | @ExperimentalMaterial3Api
29 | @Composable
30 | fun OfflineDownloadScreen(
31 | offlineFileViewModel: OfflineFileViewModel,
32 | fileViewModel: FileViewModel
33 | ) {
34 | offlineFileViewModel.quota()
35 | val quotaBean by offlineFileViewModel.quotaBean.collectAsState()
36 | val path by fileViewModel.currentPath.collectAsState()
37 | val clickFun = { command: String, url: String ->
38 | when (command) {
39 | "sha1" -> {}
40 | "offline" -> {
41 | val urlList = url.split("\n").filter { i ->
42 | i.startsWith("http", true) || i.startsWith(
43 | "ftp",
44 | true
45 | ) || i.startsWith("magnet", true) || i.startsWith("ed2k", true)
46 | }.toList()
47 | offlineFileViewModel.addTask(urlList, fileViewModel.currentCid)
48 | }
49 | }
50 | }
51 |
52 | val minHeightPercentage = 0.5f // 最小高度百分比
53 | val maxHeightPercentage = 0.65f // 最大高度百分比
54 |
55 | var urlText by remember {
56 | mutableStateOf("")
57 | }
58 | var urlCount by remember {
59 | mutableStateOf("链接")
60 | }
61 | Column {
62 | TopAppBar(
63 | title = {
64 | Text(text = stringResource(R.string.app_name))
65 | },
66 | colors = TopAppBarDefaults.topAppBarColors(containerColor = Purple80),
67 | navigationIcon = {
68 | TopAppBarActionButton(
69 | imageVector = Icons.Rounded.Menu,
70 | description = "navigationIcon"
71 | ) {
72 | App.instance.openDrawerState()
73 | }
74 | },
75 | )
76 | Surface(
77 | modifier = Modifier
78 | .fillMaxSize()
79 | ) {
80 | Column(
81 | modifier = Modifier
82 | .padding(top = 28.dp),
83 | horizontalAlignment = Alignment.CenterHorizontally
84 | ) {
85 | OutlinedTextField(
86 | value = urlText,
87 | label = { Text(text = urlCount) },
88 | placeholder = { Text(text = "支持HTTP、HTTPS、FTP、磁力链和电驴链接,换行可添加多个") },
89 | onValueChange = {
90 | urlText = it
91 | if (it != "") {
92 | val size = it.split("\n")
93 | .filter { i ->
94 | i.startsWith("http", true) || i.startsWith(
95 | "ftp",
96 | true
97 | ) || i.startsWith("magnet", true) || i.startsWith("ed2k", true)
98 | }
99 | .size
100 | urlCount = "当前总共${size}个链接"
101 | }
102 | },
103 | modifier = Modifier
104 | .width((maxHeightPercentage * LocalConfiguration.current.screenWidthDp).dp)
105 | .heightIn(
106 | min = (minHeightPercentage * LocalConfiguration.current.screenHeightDp).dp,
107 | max = (maxHeightPercentage * LocalConfiguration.current.screenHeightDp).dp
108 | )
109 | )
110 | Spacer(modifier = Modifier.height(10.dp))
111 | Text(text = "本月配额:剩${quotaBean.surplus}/总${quotaBean.count}个")
112 | Spacer(modifier = Modifier.height(10.dp))
113 |
114 | OutlinedTextField(
115 | value = path,
116 | label = { Text(text = "离线位置") },
117 | readOnly = true,
118 | onValueChange = { },
119 | modifier = Modifier.width((maxHeightPercentage * LocalConfiguration.current.screenWidthDp).dp)
120 | )
121 | Row(
122 | verticalAlignment = Alignment.CenterVertically,
123 | modifier = Modifier
124 | .padding(top = 28.dp),
125 | ) {
126 | Button(onClick = {
127 | clickFun.invoke("offline", urlText)
128 | urlText = ""
129 | urlCount = "链接"
130 | }) {
131 | Text(text = "开始离线下载")
132 | }
133 | }
134 | }
135 | }
136 | }
137 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/OfflineFileScreen.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.ClipData
5 | import android.content.ClipboardManager
6 | import android.content.Context
7 | import android.widget.Toast
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.lazy.LazyColumn
13 | import androidx.compose.foundation.lazy.itemsIndexed
14 | import androidx.compose.material.ExperimentalMaterialApi
15 | import androidx.compose.material.pullrefresh.PullRefreshIndicator
16 | import androidx.compose.material.pullrefresh.pullRefresh
17 | import androidx.compose.material.pullrefresh.rememberPullRefreshState
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.collectAsState
20 | import androidx.compose.runtime.getValue
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.platform.LocalContext
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.unit.dp
26 | import androidx.core.content.ContextCompat.getSystemService
27 | import github.zerorooot.nap511.R
28 | import github.zerorooot.nap511.screenitem.OfflineCellItem
29 | import github.zerorooot.nap511.util.App
30 | import github.zerorooot.nap511.util.ConfigKeyUtil
31 | import github.zerorooot.nap511.viewmodel.FileViewModel
32 | import github.zerorooot.nap511.viewmodel.OfflineFileViewModel
33 | import java.util.*
34 |
35 | @OptIn(ExperimentalMaterialApi::class)
36 | @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnusedMaterialScaffoldPaddingParameter")
37 | @Composable
38 | fun OfflineFileScreen(
39 | offlineFileViewModel: OfflineFileViewModel,
40 | fileViewModel: FileViewModel,
41 | ) {
42 | offlineFileViewModel.getOfflineFileList()
43 | val offlineInfo by offlineFileViewModel.offlineInfo.collectAsState()
44 | val refreshing by offlineFileViewModel.isRefreshing.collectAsState()
45 | val offlineList by offlineFileViewModel.offlineFile.collectAsState()
46 | val context = LocalContext.current
47 |
48 | OfflineFileInfoDialog(offlineFileViewModel) {
49 | offlineFileViewModel.closeOfflineDialog()
50 | }
51 |
52 | val itemOnClick = { i: Int ->
53 | val offlineTask = offlineList[i]
54 | val cid = if (offlineTask.fileId == "") offlineTask.wpPathId else offlineTask.fileId
55 | App.selectedItem = ConfigKeyUtil.MY_FILE
56 | fileViewModel.getFiles(cid)
57 | }
58 | val menuOnClick = { name: String, index: Int ->
59 | when (name) {
60 | "复制下载链接" -> copyDownloadUrl(context, offlineList[index].url)
61 | "删除文件" -> offlineFileViewModel.delete(offlineList[index])
62 | "文件信息" -> offlineFileViewModel.openOfflineDialog(index)
63 | }
64 | }
65 | val appBarOnClick = { name: String ->
66 | when (name) {
67 | "刷新文件" -> offlineFileViewModel.refresh()
68 | "清空已完成" -> offlineFileViewModel.clearFinish()
69 | "清空已失败" -> offlineFileViewModel.clearError()
70 | "复制所有下载链接" -> {
71 | val stringJoiner = StringJoiner("\n")
72 | offlineList.forEach { i -> stringJoiner.add(i.url) }
73 | copyDownloadUrl(context, stringJoiner.toString())
74 | }
75 |
76 | "ModalNavigationDrawerMenu" -> App.instance.openDrawerState()
77 | }
78 | }
79 |
80 | val pullRefreshState = rememberPullRefreshState(refreshing, { offlineFileViewModel.refresh() })
81 |
82 | Column {
83 | AppTopBarOfflineFile(stringResource(R.string.app_name), appBarOnClick)
84 |
85 | MiddleEllipsisText(
86 | text = "当前下载量:${offlineInfo.count},配额:${offlineInfo.quota}/${offlineInfo.total}",
87 | modifier = Modifier.padding(8.dp, 4.dp)
88 | )
89 |
90 | Box(Modifier.pullRefresh(pullRefreshState)) {
91 | LazyColumn(
92 | modifier = Modifier.fillMaxSize(),
93 | ) {
94 | itemsIndexed(items = offlineList, key = { _, item ->
95 | item.hashCode()
96 | }) { index, item ->
97 | OfflineCellItem(
98 | offlineTask = item,
99 | index = index,
100 | itemOnClick = itemOnClick,
101 | menuOnClick = menuOnClick
102 | )
103 | }
104 | }
105 |
106 | PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
107 | }
108 |
109 | // Scaffold() {
110 | // SwipeRefresh(state = rememberSwipeRefreshState(refreshing), onRefresh = {
111 | // offlineFileViewModel.refresh()
112 | // }) {
113 | // LazyColumn(
114 | // modifier = Modifier.fillMaxSize(),
115 | // ) {
116 | // itemsIndexed(items = offlineList, key = { _, item ->
117 | // item.hashCode()
118 | // }) { index, item ->
119 | // OfflineCellItem(
120 | // offlineTask = item,
121 | // index = index,
122 | // itemOnClick = itemOnClick,
123 | // menuOnClick = menuOnClick
124 | // )
125 | // }
126 | // }
127 | // }
128 | //
129 | // }
130 | }
131 |
132 | }
133 |
134 | fun copyDownloadUrl(context: Context, text: String) {
135 | val clipboard = getSystemService(context, ClipboardManager::class.java)
136 | val clip = ClipData.newPlainText("label", text)
137 | clipboard?.setPrimaryClip(clip)
138 | Toast.makeText(context, "复制成功~", Toast.LENGTH_SHORT).show()
139 | }
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-parcelize'
5 | }
6 |
7 | android {
8 | namespace 'github.zerorooot.nap511'
9 | compileSdk 35
10 |
11 | defaultConfig {
12 | applicationId "github.zerorooot.nap511"
13 | minSdk 26
14 | targetSdk 35
15 | versionCode 8
16 | versionName "1.2.4"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 | vectorDrawables {
20 | useSupportLibrary true
21 | }
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_11
32 | targetCompatibility JavaVersion.VERSION_11
33 | }
34 | kotlinOptions {
35 | jvmTarget = '11'
36 | }
37 | buildFeatures {
38 | compose true
39 | }
40 | composeOptions {
41 | kotlinCompilerExtensionVersion '1.5.1'
42 | }
43 | packagingOptions {
44 | resources {
45 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
46 | }
47 | }
48 | testOptions {
49 | unitTests.returnDefaultValues = true
50 | }
51 | }
52 |
53 | dependencies {
54 | // Kotlin
55 | implementation("androidx.concurrent:concurrent-futures-ktx:1.2.0")
56 | //log
57 | implementation 'com.elvishew:xlog:1.11.1'
58 |
59 | implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
60 | //restart app
61 | implementation 'com.jakewharton:process-phoenix:3.0.0'
62 | //setting screen
63 | // implementation "com.github.JamalMulla:ComposePrefs3:1.0.3"
64 | implementation "com.github.JamalMulla:ComposePrefs:1.0.6"
65 | implementation "androidx.datastore:datastore-preferences:1.1.1"
66 |
67 | // implementation 'androidx.browser:browser:1.5.0'
68 | // implementation 'com.android.support:customtabs:24.1.1'
69 | implementation 'com.github.acsbendi:Android-Request-Inspector-WebView:1.0.5'
70 | // implementation 'com.github.alorma:compose-settings-ui-m3:0.25.0'
71 | // implementation 'com.github.alorma:compose-settings-storage-preferences:0.25.0'
72 | // implementation 'com.github.alorma:compose-settings-storage-datastore-proto:0.25.0'
73 |
74 | // implementation 'com.chillibits:simplesettings:1.3.4'
75 | // implementation "androidx.preference:preference-ktx:1.2.0"
76 |
77 | implementation "com.google.accompanist:accompanist-pager:0.29.1-alpha"
78 | implementation "com.google.accompanist:accompanist-pager-indicators:0.29.1-alpha"
79 |
80 | implementation 'com.github.SmartToolFactory:Compose-Zoom:0.5.0'
81 |
82 | implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0"
83 | // implementation 'com.github.CarGuo.GSYVideoPlayer:GSYVideoPlayer:v8.3.4-release-jitpack'
84 | implementation 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-java:v8.3.4-release-jitpack'
85 | implementation 'com.github.CarGuo.GSYVideoPlayer:GSYVideoPlayer-exo2:v8.3.4-release-jitpack'
86 | implementation 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-arm64:v8.3.4-release-jitpack'
87 |
88 | implementation "io.coil-kt:coil-compose:2.2.2"
89 | //network
90 | implementation "com.squareup.retrofit2:retrofit:2.9.0"
91 | //json to bean
92 | implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.12"
93 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
94 | //coroutines
95 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
96 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
97 |
98 | implementation 'androidx.appcompat:appcompat:1.7.0'
99 | implementation 'com.google.android.material:material:1.12.0'
100 | implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
101 | implementation "androidx.compose.material:material-icons-extended:$compose_version"
102 | // implementation "com.google.accompanist:accompanist-swiperefresh:0.29.1-alpha"
103 | // implementation 'androidx.window:window-core:'
104 | implementation "androidx.navigation:navigation-compose:2.8.3"
105 | implementation 'androidx.preference:preference:1.2.1'
106 |
107 | def lifecycle_version = "2.8.7"
108 | // ViewModel
109 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
110 | // ViewModel utilities for Compose
111 | implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
112 | // LiveData
113 | implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
114 |
115 | implementation 'androidx.work:work-runtime-ktx:2.10.0'
116 | implementation "androidx.compose.runtime:runtime-livedata:1.7.8"
117 |
118 | implementation 'androidx.core:core-ktx:1.15.0'
119 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
120 | implementation 'androidx.activity:activity-compose:1.9.3'
121 | implementation "androidx.compose.ui:ui:$compose_version"
122 | implementation "androidx.compose.ui:ui-util:$compose_version"
123 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
124 | implementation 'androidx.compose.material3:material3:1.3.1'
125 | // implementation "androidx.compose.material3:material3:1.0.1"
126 |
127 | testImplementation 'junit:junit:4.13.2'
128 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3"
129 | testImplementation "org.mockito:mockito-core:1.9.5"
130 |
131 | androidTestImplementation 'androidx.test.ext:junit:1.2.1'
132 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
133 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
134 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
135 | debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
136 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
101 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/LogScreen.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 | import android.app.Application
4 | import android.content.ContentValues
5 | import android.os.Build
6 | import android.os.Environment
7 | import android.provider.MediaStore
8 | import androidx.compose.foundation.horizontalScroll
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.Column
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.rememberScrollState
13 | import androidx.compose.foundation.text.selection.SelectionContainer
14 | import androidx.compose.foundation.verticalScroll
15 | import androidx.compose.material.ExperimentalMaterialApi
16 | import androidx.compose.material3.MaterialTheme
17 | import androidx.compose.material3.Text
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.LaunchedEffect
20 | import androidx.compose.runtime.mutableStateOf
21 | import androidx.compose.runtime.remember
22 | import androidx.compose.runtime.rememberCoroutineScope
23 | import androidx.compose.ui.Modifier
24 | import com.elvishew.xlog.XLog
25 | import github.zerorooot.nap511.util.App
26 | import github.zerorooot.nap511.util.ConfigKeyUtil
27 | import kotlinx.coroutines.delay
28 | import kotlinx.coroutines.launch
29 | import java.io.BufferedInputStream
30 | import java.io.ByteArrayOutputStream
31 | import java.io.File
32 | import java.io.FileInputStream
33 | import java.io.IOException
34 | import java.io.InputStream
35 |
36 | @OptIn(ExperimentalMaterialApi::class)
37 | @Composable
38 | fun LogScreen() {
39 | var log = remember {
40 | mutableStateOf(
41 | try {
42 | readInputStreamAsString(
43 | FileInputStream(
44 | File(
45 | App.instance.cacheDir,
46 | "log"
47 | )
48 | )
49 | )
50 | } catch (_: Exception) {
51 | ""
52 | }
53 | )
54 | }
55 |
56 | val verticalScrollState = rememberScrollState()
57 | val horizontalScrollState = rememberScrollState()
58 | val coroutine = rememberCoroutineScope()
59 | val appBarOnClick = { name: String ->
60 | when (name) {
61 | "滚动顶部" -> {
62 | coroutine.launch {
63 | verticalScrollState.animateScrollTo(0)
64 | }
65 | }
66 |
67 | "滚动底部" -> {
68 | coroutine.launch {
69 | verticalScrollState.animateScrollTo(verticalScrollState.maxValue)
70 | }
71 | }
72 |
73 | "清空日志" -> {
74 | File(
75 | App.instance.cacheDir,
76 | "log"
77 | ).delete()
78 | log.value = ""
79 | }
80 |
81 | "导出日志" -> {
82 | writeToPublicExternalStorage(App.instance, "log.txt", log.value)
83 | }
84 |
85 | "ModalNavigationDrawerMenu" -> App.instance.openDrawerState()
86 | else -> {}
87 | }
88 | }
89 |
90 | Column {
91 | AppTopBarLogScreen(ConfigKeyUtil.LOG_SCREEN, appBarOnClick as (String) -> Unit)
92 | Box(
93 | modifier = Modifier
94 | .fillMaxSize()
95 | .verticalScroll(verticalScrollState)
96 | .horizontalScroll(horizontalScrollState)
97 | ) {
98 |
99 | Text(
100 | text = log.value,
101 | style = MaterialTheme.typography.bodyLarge
102 | )
103 | }
104 | }
105 |
106 |
107 |
108 | LaunchedEffect(Unit) {
109 | delay(10)
110 | verticalScrollState.animateScrollTo(verticalScrollState.maxValue)
111 | }
112 |
113 | }
114 |
115 | //private fun checkAndRequestPermissions() {
116 | // if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
117 | // requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
118 | // }
119 | //}
120 |
121 | fun writeToPublicExternalStorage(
122 | applicationContext: Application,
123 | fileName: String,
124 | content: String
125 | ) {
126 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
127 | // Scoped Storage for Android 10+
128 | val resolver = applicationContext.contentResolver
129 | val values = ContentValues().apply {
130 | put(MediaStore.Downloads.DISPLAY_NAME, fileName)
131 | put(MediaStore.Downloads.MIME_TYPE, "text/plain")
132 | put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
133 | }
134 | val uri =
135 | resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
136 | uri?.let {
137 | resolver.openOutputStream(it)?.use { outputStream ->
138 | outputStream.write(content.toByteArray())
139 | App.instance.toast("导出成功,日志文件保存至 Downloads 目录")
140 | XLog.d("FileWrite File written to Downloads: $uri")
141 | }
142 | }
143 | } else {
144 | // Legacy method for Android 9 and below
145 | val file = File(
146 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
147 | fileName
148 | )
149 | try {
150 | file.writeText(content)
151 | XLog.d("FileWrite File written to: ${file.absolutePath}")
152 | } catch (e: IOException) {
153 | e.printStackTrace()
154 | XLog.e("FileWrite Error writing file: $e")
155 | }
156 | }
157 | }
158 |
159 |
160 | fun readInputStreamAsString(`in`: InputStream): String {
161 | val bis = BufferedInputStream(`in`)
162 | val buf = ByteArrayOutputStream()
163 | var result = bis.read()
164 | while (result != -1) {
165 | val b = result.toByte()
166 | buf.write(b.toInt())
167 | result = bis.read()
168 | }
169 | return buf.toString()
170 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/viewmodel/RecycleViewModel.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.viewmodel
2 |
3 | import android.app.Application
4 | import android.widget.Toast
5 | import androidx.compose.runtime.mutableStateListOf
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import github.zerorooot.nap511.R
9 | import github.zerorooot.nap511.bean.RecycleBean
10 | import github.zerorooot.nap511.bean.RecycleInfo
11 | import github.zerorooot.nap511.service.FileService
12 | import github.zerorooot.nap511.util.ConfigKeyUtil
13 | import github.zerorooot.nap511.util.DataStoreUtil
14 | import github.zerorooot.nap511.util.DialogSwitchUtil
15 | //import github.zerorooot.nap511.util.SharedPreferencesUtil
16 | import kotlinx.coroutines.flow.MutableStateFlow
17 | import kotlinx.coroutines.flow.asStateFlow
18 | import kotlinx.coroutines.launch
19 | import java.text.SimpleDateFormat
20 | import java.util.*
21 | import kotlin.collections.ArrayList
22 |
23 | class RecycleViewModel(private val cookie: String, private val application: Application) :
24 | ViewModel() {
25 | private val _isRefreshing = MutableStateFlow(false)
26 | var isRefreshing = _isRefreshing.asStateFlow()
27 |
28 | private val _recycleInfo = MutableStateFlow(RecycleInfo())
29 |
30 | var recycleFileList = mutableStateListOf()
31 |
32 | val dialogSwitchUtil = DialogSwitchUtil.getInstance()
33 |
34 | private val fileService: FileService by lazy {
35 | FileService.getInstance(cookie)
36 | }
37 |
38 |
39 | fun getRecycleFileList() {
40 | if (recycleFileList.isNotEmpty()) {
41 | return
42 | }
43 | viewModelScope.launch {
44 | _isRefreshing.value = true
45 | _recycleInfo.value = fileService.recycleList()
46 | val recycleBeanList = _recycleInfo.value.recycleBeanList
47 | if (recycleBeanList.isNotEmpty()) {
48 | setRecycleBean(recycleBeanList)
49 | recycleFileList.clear()
50 | recycleFileList.addAll(recycleBeanList)
51 | }
52 |
53 | _isRefreshing.value = false
54 | }
55 | }
56 |
57 | fun delete(index: Int) {
58 | val password = DataStoreUtil.getData(ConfigKeyUtil.PASSWORD, "")
59 | if (password == "") {
60 | dialogSwitchUtil.isOpenRecyclePasswordDialog = true
61 | return
62 | }
63 | delete(index, password)
64 | }
65 |
66 | fun delete(index: Int, password: String, save: Boolean = false) {
67 | viewModelScope.launch {
68 | val revert = fileService.recycleClean(recycleFileList[index].id, password)
69 | val message = if (revert.state) {
70 | recycleFileList.removeAt(index)
71 | if (save) {
72 | DataStoreUtil.putData(ConfigKeyUtil.PASSWORD, password)
73 | }
74 | "删除成功"
75 | } else {
76 | DataStoreUtil.putData(ConfigKeyUtil.PASSWORD, "")
77 | "删除失败,${revert.errorMsg}"
78 | }
79 | Toast.makeText(application, message, Toast.LENGTH_SHORT).show()
80 | }
81 | }
82 |
83 | fun deleteAll() {
84 | val password = DataStoreUtil.getData(ConfigKeyUtil.PASSWORD, "")
85 | if (password == "") {
86 | dialogSwitchUtil.isOpenRecyclePasswordDialog = true
87 | return
88 | }
89 | viewModelScope.launch {
90 | val recycleCleanAll = fileService.recycleCleanAll(password)
91 | val message = if (recycleCleanAll.state) {
92 | recycleFileList.clear()
93 | "清除成功"
94 | } else {
95 | "清除失败,${recycleCleanAll.errorMsg}"
96 | }
97 | Toast.makeText(application, message, Toast.LENGTH_SHORT).show()
98 | }
99 | }
100 |
101 | fun revert(index: Int) {
102 | viewModelScope.launch {
103 | val revert = fileService.revert(recycleFileList[index].id)
104 | val message = if (revert.state) {
105 | recycleFileList.removeAt(index)
106 | "恢复成功"
107 | } else {
108 | "恢复失败,${revert.errorMsg}"
109 | }
110 | Toast.makeText(application, message, Toast.LENGTH_SHORT).show()
111 | }
112 | }
113 |
114 | fun closeDialog() {
115 | dialogSwitchUtil.isOpenRecyclePasswordDialog = false
116 | }
117 |
118 | fun refresh() {
119 | recycleFileList.clear()
120 | getRecycleFileList()
121 | }
122 |
123 |
124 | private fun setRecycleBean(recycleBeanList: ArrayList) {
125 | recycleBeanList.forEach { recycleBean ->
126 | recycleBean.modifiedTimeString =
127 | SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(
128 | recycleBean.modifiedTime.toLong() * 1000
129 | )
130 |
131 | recycleBean.fileSizeString = android.text.format.Formatter.formatFileSize(
132 | application,
133 | if (recycleBean.fileSize == "") "0".toLong() else recycleBean.fileSize.toLong()
134 | ) + " "
135 | setIco(recycleBean)
136 | }
137 | }
138 |
139 | private fun setIco(recycleBean: RecycleBean) {
140 | if (recycleBean.type == "2") {
141 | recycleBean.fileIco = R.drawable.folder
142 | return
143 | }
144 | if (recycleBean.iv == 1) {
145 | recycleBean.fileIco = R.drawable.mp4
146 | return
147 | }
148 | when (recycleBean.ico) {
149 | "apk" -> recycleBean.fileIco = R.drawable.apk
150 | "iso" -> recycleBean.fileIco = R.drawable.iso
151 | "zip" -> recycleBean.fileIco = R.drawable.zip
152 | "7z" -> recycleBean.fileIco = R.drawable.zip
153 | "rar" -> recycleBean.fileIco = R.drawable.zip
154 | "png" -> recycleBean.fileIco = R.drawable.png
155 | "jpg" -> recycleBean.fileIco = R.drawable.png
156 | "mp3" -> recycleBean.fileIco = R.drawable.mp3
157 | "txt" -> recycleBean.fileIco = R.drawable.txt
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/service/Sha1Service.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.service
2 |
3 |
4 | import android.app.Service
5 | import android.content.Intent
6 | import android.os.Build
7 | import android.os.Handler
8 | import android.os.IBinder
9 | import android.os.Looper
10 | import android.widget.Toast
11 | import androidx.annotation.RequiresApi
12 | import com.google.gson.Gson
13 | import com.google.gson.JsonArray
14 | import com.google.gson.JsonObject
15 | import com.google.gson.JsonParser
16 | import github.zerorooot.nap511.bean.FileBean
17 | import github.zerorooot.nap511.util.ConfigKeyUtil
18 | import github.zerorooot.nap511.util.DataStoreUtil
19 | import github.zerorooot.nap511.util.Sha1Util
20 | import okhttp3.FormBody
21 | import okhttp3.MediaType.Companion.toMediaTypeOrNull
22 | import okhttp3.OkHttpClient
23 | import okhttp3.Request
24 | import okhttp3.RequestBody.Companion.toRequestBody
25 | import kotlin.concurrent.thread
26 |
27 |
28 | class Sha1Service : Service() {
29 | private val sha1Util = Sha1Util()
30 | private val okHttpClient = OkHttpClient()
31 |
32 | override fun onBind(intent: Intent?): IBinder? {
33 | TODO("Not yet implemented")
34 | }
35 |
36 |
37 | @RequiresApi(Build.VERSION_CODES.TIRAMISU)
38 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
39 | val command = intent!!.getStringExtra(ConfigKeyUtil.COMMAND)
40 | val cookie = intent.getStringExtra("cookie")!!
41 | thread {
42 | when (command) {
43 | ConfigKeyUtil.SENT_TO_ARIA2 -> {
44 | val fileBeanList =
45 | arrayListOf(
46 | Gson().fromJson(
47 | intent.getStringExtra("list"),
48 | FileBean::class.java
49 | )
50 | )
51 | val downloadUrl = getDownloadUrl(fileBeanList, cookie)
52 | sendToAria2(downloadUrl)
53 | }
54 | }
55 | }
56 |
57 | return super.onStartCommand(intent, flags, startId)
58 | }
59 |
60 |
61 | private fun getDownloadUrl(
62 | fileBeanList: ArrayList,
63 | cookie: String
64 | ): ArrayList {
65 | val list = arrayListOf()
66 | fileBeanList.forEach { fileBean ->
67 | val response =
68 | getDownloadUrl(fileBean.pickCode, fileBean.fileId, cookie)
69 | list.add(response)
70 | }
71 | return list
72 | }
73 |
74 | private fun getDownloadUrl(
75 | pickCode: String,
76 | fileId: String,
77 | cookie: String
78 | ): String {
79 | val tm = System.currentTimeMillis() / 1000
80 | val m115Encode = sha1Util.m115_encode(pickCode, tm)
81 | val map = FormBody.Builder().add("data", m115Encode.data).build()
82 |
83 | val request: Request = Request
84 | .Builder()
85 | .url("https://proapi.115.com/app/chrome/downurl?t=$tm")
86 | .addHeader("cookie", cookie)
87 | .addHeader("Content-Type", "application/x-www-form-urlencoded")
88 | .addHeader(
89 | "User-Agent",
90 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 115Browser/23.9.3.6"
91 | )
92 | .post(map)
93 | .build()
94 |
95 | val response = okHttpClient.newCall(request).execute()
96 |
97 | val returnJson = JsonParser().parse(response.body.string()).asJsonObject
98 | val data = returnJson.get("data").asString
99 | val m115Decode = sha1Util.m115_decode(data, m115Encode.key)
100 |
101 |
102 | //{"fileId":{"file_name":"a","file_size":"0","pick_code":"pick_code","url":false}}
103 |
104 | return JsonParser().parse(m115Decode).asJsonObject.getAsJsonObject(fileId)
105 | .getAsJsonObject("url").get("url").asString
106 |
107 | }
108 |
109 | /**
110 | *
111 | {
112 | "jsonrpc": "2.0",
113 | "method": "aria2.addUri",
114 | "id": 1,
115 | "params": [
116 | "token:xxx",
117 | [
118 | "xxx"
119 | ],
120 | {
121 | "header": "User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 115Browser/23.9.3.6"
122 | }
123 | ]
124 | }
125 | */
126 | private fun sendToAria2(fileBeanDownloadList: ArrayList) {
127 | // val aria2Token = sharedPreferencesUtil.get(ConfigUtil.aria2Token)
128 | // val aria2Url = sharedPreferencesUtil.get(
129 | // ConfigUtil.aria2Url,
130 | // ConfigUtil.aria2UrldefValue
131 | // ) + "?tm=${System.currentTimeMillis()}"
132 | val aria2Token = DataStoreUtil.getData(ConfigKeyUtil.ARIA2_TOKEN, "")
133 | val aria2Url = DataStoreUtil.getData(
134 | ConfigKeyUtil.ARIA2_URL,
135 | ConfigKeyUtil.ARIA2_URL_DEFAULT_VALUE
136 | ) + "?tm=${System.currentTimeMillis()}"
137 |
138 | val paramsJsonArray = JsonArray()
139 | if (aria2Token != "") {
140 | paramsJsonArray.add("token:$aria2Token")
141 | }
142 |
143 | val urlJsonArray = JsonArray()
144 | fileBeanDownloadList.forEach { i -> urlJsonArray.add(i) }
145 | paramsJsonArray.add(urlJsonArray)
146 |
147 |
148 | val headersJsonObject = JsonObject()
149 | headersJsonObject.addProperty(
150 | "header",
151 | "User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 115Browser/23.9.3.6"
152 | )
153 | paramsJsonArray.add(headersJsonObject)
154 |
155 | val jsonObject = JsonObject()
156 | jsonObject.addProperty("jsonrpc", "2.0")
157 | jsonObject.addProperty("method", "aria2.addUri")
158 | jsonObject.addProperty("id", "nap511")
159 |
160 | jsonObject.add("params", paramsJsonArray)
161 |
162 |
163 | val request: Request = Request
164 | .Builder()
165 | .url(aria2Url)
166 | .post(
167 | jsonObject.toString()
168 | .toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
169 | )
170 | .build()
171 | val response = okHttpClient.newCall(request).execute()
172 |
173 | val bodyJson = JsonParser().parse(response.body.string()).asJsonObject
174 |
175 | Handler(Looper.getMainLooper()).post {
176 | if (bodyJson.has("error")) {
177 | Toast.makeText(
178 | application,
179 | "下载失败,${bodyJson.getAsJsonObject("error").get("message").asString}",
180 | Toast.LENGTH_SHORT
181 | ).show()
182 | } else {
183 | Toast.makeText(application, "发送成功!", Toast.LENGTH_SHORT).show()
184 | }
185 | }
186 |
187 | }
188 |
189 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/activity/TorrentTaskActivity.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.activity
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.net.Uri
7 | import android.os.Bundle
8 | import com.google.gson.Gson
9 | import github.zerorooot.nap511.bean.BaseReturnMessage
10 | import github.zerorooot.nap511.bean.InitUploadBean
11 | import github.zerorooot.nap511.util.App
12 | import github.zerorooot.nap511.util.ConfigKeyUtil
13 | import github.zerorooot.nap511.util.DataStoreUtil
14 | import okhttp3.Call
15 | import okhttp3.Callback
16 | import okhttp3.MediaType.Companion.toMediaType
17 | import okhttp3.MultipartBody
18 | import okhttp3.OkHttpClient
19 | import okhttp3.Request
20 | import okhttp3.RequestBody
21 | import okhttp3.RequestBody.Companion.asRequestBody
22 | import okhttp3.RequestBody.Companion.toRequestBody
23 | import okhttp3.Response
24 | import java.io.File
25 | import java.io.FileOutputStream
26 | import java.io.InputStream
27 | import java.io.OutputStream
28 |
29 |
30 | class TorrentTaskActivity : Activity() {
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
34 | val torrentFile = fileFromContentUri(this, intent.data!!)
35 | val uid = App.uid
36 | val defaultOfflineCid = DataStoreUtil.getData(ConfigKeyUtil.DEFAULT_OFFLINE_CID, "0")
37 | initUpload(torrentFile, App.cookie, uid, defaultOfflineCid)
38 | }
39 | moveTaskToBack(true);
40 | finishAndRemoveTask()
41 | }
42 |
43 |
44 | private fun initUpload(torrentFile: File, cookie: String, uid: String, target: String) {
45 | val url = "https://uplb.115.com/3.0/sampleinitupload.php"
46 | val postBody =
47 | "userid=$uid&filename=${torrentFile.name}&filesize=${torrentFile.length()}&target=U_1_$target".toRequestBody()
48 |
49 | val okHttpClient = OkHttpClient()
50 | val request: Request = Request.Builder().url(url)
51 | .addHeader("cookie", cookie)
52 | .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
53 | .addHeader(
54 | "User-Agent",
55 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 115Browser/23.9.3.6"
56 | ).post(postBody).build()
57 | okHttpClient.newCall(request).enqueue(object : Callback {
58 | override fun onFailure(call: Call, e: okio.IOException) {
59 | TODO("Not yet implemented")
60 | }
61 |
62 | override fun onResponse(call: Call, response: Response) {
63 | val body = response.body
64 | val string = body.string()
65 | val initUploadBean = Gson().fromJson(
66 | string, InitUploadBean::class.java
67 | )
68 | // println(initUploadBean)
69 |
70 | val client = OkHttpClient()
71 | val requestBody: RequestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
72 | .addFormDataPart("name", torrentFile.name)
73 | .addFormDataPart("key", initUploadBean.key)
74 | .addFormDataPart("policy", initUploadBean.policy)
75 | .addFormDataPart("OSSAccessKeyId", initUploadBean.oSSAccessKeyId)
76 | .addFormDataPart("success_action_status", "200")
77 | .addFormDataPart("callback", initUploadBean.callback)
78 | .addFormDataPart("signature", initUploadBean.signature)
79 | .addFormDataPart(
80 | "file",
81 | torrentFile.name,
82 | torrentFile.asRequestBody("application/x-bittorrent".toMediaType())
83 | )
84 | .build()
85 | val uploadRequest: Request =
86 | Request.Builder().url(initUploadBean.host)
87 | .addHeader("origin", "https://115.com")
88 | .addHeader("referer", "https://115.com")
89 | .addHeader("cookie", cookie)
90 | .addHeader(
91 | "User-Agent",
92 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 115Browser/23.9.3.6"
93 | ).post(requestBody).build()
94 |
95 | client.newCall(uploadRequest).enqueue(object : Callback {
96 | override fun onFailure(call: Call, e: okio.IOException) {
97 | TODO("Not yet implemented")
98 | }
99 |
100 | override fun onResponse(call: Call, response: Response) {
101 | val uploadBody = response.body.string()
102 |
103 | /**
104 | * {
105 | * "state": true,
106 | * "message": "",
107 | * "code": 0,
108 | * "data": {
109 | * "aid": xxx,
110 | * "cid": "0",
111 | * "file_name": "xxxx",
112 | * "file_ptime": xxx,
113 | * "file_status": 1,
114 | * "file_id": "xxx",
115 | * "file_size": "229771",
116 | * "pick_code": "xxx",
117 | * "sha1": "xxx",
118 | * "sp": 1,
119 | * "file_type": 188,
120 | * "is_video": 0
121 | * }
122 | * }
123 | */
124 | val uploadJson = Gson().fromJson(
125 | uploadBody, BaseReturnMessage::class.java
126 | )
127 | val message = if (uploadJson.state) {
128 | "上传种子文件成功,种子文件保存到默认离线位置中"
129 | } else {
130 | "上传种子文件失败,${uploadJson.message}"
131 | }
132 | App.instance.toast(message)
133 |
134 | }
135 |
136 | })
137 |
138 |
139 | }
140 | })
141 |
142 |
143 | }
144 |
145 |
146 | private fun fileFromContentUri(context: Context, contentUri: Uri): File {
147 | val fileName = File(contentUri.path!!).name
148 | val tempFile = File(context.cacheDir, fileName)
149 | if (tempFile.exists()) {
150 | tempFile.delete()
151 | }
152 | tempFile.createNewFile()
153 | try {
154 | val oStream = FileOutputStream(tempFile)
155 | val inputStream = context.contentResolver.openInputStream(contentUri)
156 | inputStream?.let {
157 | copy(inputStream, oStream)
158 | }
159 | oStream.flush()
160 | } catch (e: Exception) {
161 | e.printStackTrace()
162 | }
163 |
164 | return tempFile
165 | }
166 |
167 | @Throws(java.io.IOException::class)
168 | private fun copy(source: InputStream, target: OutputStream) {
169 | val buf = ByteArray(8192)
170 | var length: Int
171 | while (source.read(buf).also { length = it } > 0) {
172 | target.write(buf, 0, length)
173 | }
174 | }
175 | }
176 |
177 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/AppTopBar.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.rounded.ArrowBack
6 | import androidx.compose.material.icons.rounded.Menu
7 | import androidx.compose.material.icons.rounded.Search
8 | import androidx.compose.material3.*
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.graphics.painter.Painter
11 | import androidx.compose.ui.graphics.vector.ImageVector
12 | import androidx.compose.ui.res.painterResource
13 | import github.zerorooot.nap511.R
14 | import github.zerorooot.nap511.ui.theme.Purple80
15 |
16 | @OptIn(ExperimentalMaterial3Api::class)
17 | @Composable
18 | fun AppTopBarNormal(title: String, onClick: (name: String) -> Unit) {
19 | // val contextForToast = LocalContext.current.applicationContext
20 | TopAppBar(
21 | title = {
22 | Text(text = title)
23 | },
24 | colors = TopAppBarDefaults.topAppBarColors(containerColor = Purple80),
25 | navigationIcon = {
26 | TopAppBarActionButton(
27 | imageVector = Icons.Rounded.ArrowBack,
28 | description = "navigationIcon"
29 | ) {
30 | onClick.invoke("back")
31 | }
32 | },
33 | actions = {
34 | // search icon
35 | TopAppBarActionButton(
36 | imageVector = Icons.Rounded.Search,
37 | description = "Search"
38 | ) {
39 | onClick.invoke("search")
40 | }
41 | FileAppTopBarDropdownMenu(onClick = { itemValue, _ ->
42 | onClick.invoke(itemValue)
43 | })
44 | }
45 | )
46 | }
47 |
48 | @OptIn(ExperimentalMaterial3Api::class)
49 | @Composable
50 | fun AppTopBarMultiple(title: String, onClick: (String) -> Unit) {
51 | TopAppBar(
52 | title = {
53 | Text(text = title)
54 | },
55 | colors = TopAppBarDefaults.topAppBarColors(containerColor = Purple80),
56 | navigationIcon = {
57 | IconButton(onClick = { onClick.invoke("back") }) {
58 | Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "navigationIcon")
59 | }
60 | },
61 | actions = {
62 | TopAppBarActionButton(
63 | painter = painterResource(id = R.drawable.ic_baseline_arrow_upward_24),
64 | description = "up"
65 | ) {
66 | onClick.invoke("selectToUp")
67 | }
68 | TopAppBarActionButton(
69 | painter = painterResource(id = R.drawable.ic_baseline_arrow_downward_24),
70 | description = "down"
71 | ) {
72 | onClick.invoke("selectToDown")
73 | }
74 | // cut icon
75 | TopAppBarActionButton(
76 | painter = painterResource(id = R.drawable.ic_baseline_content_cut_24),
77 | description = "Cut"
78 | ) {
79 | onClick.invoke("cut")
80 | }
81 |
82 | TopAppBarActionButton(
83 | painter = painterResource(id = R.drawable.ic_baseline_delete_24),
84 | description = "delete"
85 | ) {
86 | onClick.invoke("delete")
87 | }
88 | // TopAppBarActionButton(
89 | // painter = painterResource(id = R.drawable.ic_baseline_select_all_24),
90 | // description = "ic_baseline_select_all_24"
91 | // ) {
92 | // onClick.invoke("selectAll")
93 | // }
94 | TopAppBarActionButton(
95 | painter = painterResource(id = R.drawable.ic_baseline_select_reverse_24),
96 | description = "ic_baseline_select_reverse_24"
97 | ) {
98 | onClick.invoke("selectReverse")
99 | }
100 | //R.drawable.baseline_cloud_download_24
101 | TopAppBarActionButton(
102 | painter = painterResource(id = R.drawable.baseline_cloud_24),
103 | description = "unzip file"
104 | ) {
105 | onClick.invoke("unzipAllFile")
106 | }
107 | // TopAppBarActionButton(
108 | // painter = painterResource(id = R.drawable.baseline_close_24),
109 | // description = "ic_baseline_select_all_24"
110 | // ) {
111 | // onClick.invoke("close")
112 | // }
113 | }
114 | )
115 | }
116 |
117 | @OptIn(ExperimentalMaterial3Api::class)
118 | @Composable
119 | fun AppTopBarOfflineFile(title: String, onClick: (name: String) -> Unit) {
120 | // val contextForToast = LocalContext.current.applicationContext
121 | TopAppBar(
122 | title = {
123 | Text(text = title)
124 | },
125 | colors = TopAppBarDefaults.topAppBarColors(containerColor = Purple80),
126 | navigationIcon = {
127 | TopAppBarActionButton(
128 | imageVector = Icons.Rounded.Menu,
129 | description = "navigationIcon"
130 | ) {
131 | onClick.invoke("ModalNavigationDrawerMenu")
132 | }
133 | },
134 | actions = {
135 | OfflineFileAppTopBarDropdownMenu(onClick = { itemValue, _ ->
136 | onClick.invoke(itemValue)
137 | })
138 | }
139 | )
140 | }
141 | @OptIn(ExperimentalMaterial3Api::class)
142 | @Composable
143 | fun AppTopBarLogScreen(title: String, onClick: (name: String) -> Unit) {
144 | // val contextForToast = LocalContext.current.applicationContext
145 | TopAppBar(
146 | title = {
147 | Text(text = title)
148 | },
149 | colors = TopAppBarDefaults.topAppBarColors(containerColor = Purple80),
150 | navigationIcon = {
151 | TopAppBarActionButton(
152 | imageVector = Icons.Rounded.Menu,
153 | description = "navigationIcon"
154 | ) {
155 | onClick.invoke("ModalNavigationDrawerMenu")
156 | }
157 | },
158 | actions = {
159 | LogScreenTopBarDropdownMenu(onClick = { itemValue, _ ->
160 | onClick.invoke(itemValue)
161 | })
162 | }
163 | )
164 | }
165 |
166 | @OptIn(ExperimentalMaterial3Api::class)
167 | @Composable
168 | fun AppTopBarRecycle(title: String, onClick: (name: String) -> Unit) {
169 | TopAppBar(
170 | title = {
171 | Text(text = title)
172 | },
173 | colors = TopAppBarDefaults.topAppBarColors(containerColor = Purple80),
174 | navigationIcon = {
175 | TopAppBarActionButton(
176 | imageVector = Icons.Rounded.Menu,
177 | description = "navigationIcon"
178 | ) {
179 | onClick.invoke("ModalNavigationDrawerMenu")
180 | }
181 | },
182 | actions = {
183 | RecycleAppTopBarDropdownMenu(onClick = { itemValue, _ ->
184 | onClick.invoke(itemValue)
185 | })
186 | }
187 | )
188 | }
189 |
190 | @Composable
191 | fun TopAppBarActionButton(
192 | imageVector: ImageVector? = null,
193 | painter: Painter? = null,
194 | description: String,
195 | onClick: () -> Unit
196 | ) {
197 | IconButton(onClick = {
198 | onClick()
199 | }) {
200 | if (imageVector != null) {
201 | Icon(imageVector = imageVector, contentDescription = description)
202 | }
203 | if (painter != null) {
204 | Icon(painter = painter, contentDescription = description)
205 | }
206 |
207 | }
208 | }
209 |
210 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/screen/SettingScreenNew.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.screen
2 |
3 | import android.os.Bundle
4 | import android.text.InputType
5 | import android.view.View
6 | import android.widget.FrameLayout
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.rounded.Menu
12 | import androidx.compose.material3.ExperimentalMaterial3Api
13 | import androidx.compose.material3.FabPosition
14 | import androidx.compose.material3.MaterialTheme
15 | import androidx.compose.material3.Surface
16 | import androidx.compose.material3.Text
17 | import androidx.compose.material3.TopAppBar
18 | import androidx.compose.material3.TopAppBarDefaults
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.viewinterop.AndroidView
22 | import androidx.fragment.app.commit
23 | import androidx.preference.EditTextPreference
24 | import androidx.preference.ListPreference
25 | import androidx.preference.Preference
26 | import androidx.preference.Preference.SummaryProvider
27 | import androidx.preference.PreferenceFragmentCompat
28 | import androidx.work.Data
29 | import androidx.work.OneTimeWorkRequest
30 | import androidx.work.WorkInfo
31 | import androidx.work.WorkManager
32 | import androidx.work.WorkQuery
33 | import com.elvishew.xlog.XLog
34 | import com.google.gson.Gson
35 | import com.google.gson.reflect.TypeToken
36 | import github.zerorooot.nap511.R
37 | import github.zerorooot.nap511.activity.OfflineTaskWorker
38 | import github.zerorooot.nap511.ui.theme.Purple80
39 | import github.zerorooot.nap511.util.App
40 | import github.zerorooot.nap511.util.ConfigKeyUtil
41 | import github.zerorooot.nap511.util.DataStoreUtil
42 | import github.zerorooot.nap511.util.SettingsDataStore
43 |
44 | @OptIn(ExperimentalMaterial3Api::class)
45 | @Composable
46 | fun SettingScreenNew(topAppBarActionButtonOnClick: () -> Unit) {
47 | Column {
48 | TopAppBar(
49 | title = {
50 | Text(text = ConfigKeyUtil.ADVANCED_SETTINGS)
51 | },
52 | colors = TopAppBarDefaults.topAppBarColors(containerColor = Purple80),
53 | navigationIcon = {
54 | TopAppBarActionButton(
55 | imageVector = Icons.Rounded.Menu, description = "navigationIcon"
56 | ) {
57 | topAppBarActionButtonOnClick()
58 | }
59 | },
60 | )
61 | Surface(
62 | modifier = Modifier.fillMaxSize(),
63 | color = MaterialTheme.colorScheme.background,
64 | ) {
65 | AndroidView(
66 | factory = { ctx ->
67 | val fragmentContainer = FrameLayout(ctx).apply { id = View.generateViewId() }
68 | val fragmentManager = (ctx as? AppCompatActivity)?.supportFragmentManager
69 | fragmentManager?.commit {
70 | replace(fragmentContainer.id, SettingsFragment())
71 | }
72 | fragmentContainer
73 | })
74 | }
75 |
76 | }
77 | }
78 |
79 | class SettingsFragment : PreferenceFragmentCompat() {
80 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
81 | preferenceManager.preferenceDataStore = SettingsDataStore()
82 | setPreferencesFromResource(R.xml.root_preferences, rootKey)
83 | // ✅ 取消所有 Preference 的 icon 预留空间
84 | preferenceScreen?.let { screen ->
85 | for (i in 0 until screen.preferenceCount) {
86 | screen.getPreference(i).isIconSpaceReserved = false
87 | }
88 | }
89 |
90 | findPreference("checkMagnet")?.setOnPreferenceClickListener {
91 | App.selectedItem = ConfigKeyUtil.VERIFY_MAGNET_LINK_ACCOUNT
92 | true
93 | }
94 | findPreference("checkVideo")?.setOnPreferenceClickListener {
95 | App.selectedItem = ConfigKeyUtil.VERIFY_VIDEO_ACCOUNT
96 | true
97 | }
98 | findPreference(ConfigKeyUtil.CLICK_DOWNLOAD_NOW)?.setOnPreferenceClickListener {
99 | addOfflineTask(App.cookie)
100 | true
101 | }
102 |
103 | findPreference(ConfigKeyUtil.UID)?.summaryProvider =
104 | SummaryProvider { preference ->
105 | DataStoreUtil.getData(ConfigKeyUtil.UID, "0")
106 | }
107 |
108 | findPreference(ConfigKeyUtil.PASSWORD)?.apply {
109 | setOnBindEditTextListener { editText ->
110 | editText.inputType = InputType.TYPE_CLASS_NUMBER
111 | }
112 | }
113 |
114 | findPreference(ConfigKeyUtil.ARIA2_URL)?.summaryProvider =
115 | SummaryProvider { preference ->
116 | DataStoreUtil.getData(
117 | ConfigKeyUtil.ARIA2_URL, ConfigKeyUtil.ARIA2_URL_DEFAULT_VALUE
118 | )
119 | }
120 |
121 | findPreference(ConfigKeyUtil.REQUEST_LIMIT_COUNT)?.apply {
122 | this.summaryProvider = SummaryProvider { preference ->
123 | DataStoreUtil.getData(
124 | ConfigKeyUtil.REQUEST_LIMIT_COUNT, "100"
125 | )
126 | }
127 | this.setOnBindEditTextListener { editText ->
128 | editText.inputType = InputType.TYPE_CLASS_NUMBER
129 | }
130 | }
131 | findPreference(ConfigKeyUtil.DEFAULT_OFFLINE_TIME)?.summaryProvider =
132 | SummaryProvider { preference ->
133 | "延迟" + DataStoreUtil.getData(
134 | ConfigKeyUtil.DEFAULT_OFFLINE_TIME, "5"
135 | ) + "分钟后统一离线下载"
136 | }
137 |
138 | }
139 |
140 | private fun addOfflineTask(cookie: String) {
141 | val workManager = WorkManager.getInstance(requireContext())
142 | val workQuery = WorkQuery.Builder.fromStates(
143 | listOf(
144 | WorkInfo.State.ENQUEUED, WorkInfo.State.RUNNING,
145 | // WorkInfo.State.SUCCEEDED,
146 | // WorkInfo.State.FAILED,
147 | WorkInfo.State.BLOCKED, WorkInfo.State.CANCELLED
148 | )
149 | ).build()
150 | val workInfos: List = workManager.getWorkInfos(workQuery).get()
151 | //取消所有后台任务
152 | if (workInfos.isNotEmpty()) {
153 | workInfos.forEach {
154 | workManager.cancelWorkById(it.id)
155 | }
156 | }
157 |
158 | val currentOfflineTask =
159 | DataStoreUtil.getData(ConfigKeyUtil.CURRENT_OFFLINE_TASK, "").split("\n")
160 | .filter { i -> i != "" && i != " " }.toSet().toMutableList()
161 | //currentOfflineTask等于0,证明没有离线链接缓存
162 | if (currentOfflineTask.isEmpty()) {
163 | App.instance.toast("没有离线任务!")
164 | return
165 | }
166 | XLog.d(
167 | "addOfflineTask workManager workInfos $workInfos currentOfflineTask size ${currentOfflineTask.size}"
168 | )
169 | //添加离线链接
170 | val listType = object : TypeToken?>() {}.type
171 | val list = Gson().toJson(currentOfflineTask, listType)
172 | val data: Data = Data.Builder().putString("cookie", cookie).putString("list", list).build()
173 | val request: OneTimeWorkRequest = OneTimeWorkRequest.Builder(OfflineTaskWorker::class.java)
174 | .addTag(ConfigKeyUtil.OFFLINE_TASK_WORKER).setInputData(data).build()
175 | workManager.enqueue(request)
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/util/DataStoreUtil.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.util
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.*
5 | import androidx.datastore.preferences.preferencesDataStore
6 | import androidx.preference.PreferenceDataStore
7 | import kotlinx.coroutines.flow.first
8 | import kotlinx.coroutines.flow.map
9 | import kotlinx.coroutines.runBlocking
10 |
11 |
12 | object DataStoreUtil {
13 | // 创建DataStore
14 | val App.dataStore: DataStore by preferencesDataStore(
15 | name = "Setting"
16 | )
17 |
18 | // DataStore变量
19 | private val dataStore = App.instance.dataStore
20 |
21 | /**
22 | * 存放Int数据
23 | */
24 | private suspend fun putIntData(key: String, value: Int) = dataStore.edit {
25 | it[intPreferencesKey(key)] = value
26 | }
27 |
28 | /**
29 | * 存放Set数据
30 | */
31 | private suspend fun putStringSetData(key: String, value: Set<*>) = dataStore.edit {
32 | it[stringSetPreferencesKey(key)] = value as Set
33 | }
34 |
35 | /**
36 | * 存放Long数据
37 | */
38 | private suspend fun putLongData(key: String, value: Long) = dataStore.edit {
39 | it[longPreferencesKey(key)] = value
40 | }
41 |
42 | /**
43 | * 存放String数据
44 | */
45 | private suspend fun putStringData(key: String, value: String) = dataStore.edit {
46 | it[stringPreferencesKey(key)] = value
47 | }
48 |
49 | /**
50 | * 存放Boolean数据
51 | */
52 | private suspend fun putBooleanData(key: String, value: Boolean) = dataStore.edit {
53 | it[booleanPreferencesKey(key)] = value
54 | }
55 |
56 | /**
57 | * 存放Float数据
58 | */
59 | private suspend fun putFloatData(key: String, value: Float) = dataStore.edit {
60 | it[floatPreferencesKey(key)] = value
61 | }
62 |
63 | /**
64 | * 存放Double数据
65 | */
66 | private suspend fun putDoubleData(key: String, value: Double) = dataStore.edit {
67 | it[doublePreferencesKey(key)] = value
68 | }
69 |
70 | /**
71 | * 取出Int数据
72 | */
73 | private fun getIntData(key: String, default: Int = 0): Int = runBlocking {
74 | return@runBlocking dataStore.data.map {
75 | it[intPreferencesKey(key)] ?: default
76 | }.first()
77 | }
78 |
79 | /**
80 | * 取出Long数据
81 | */
82 | private fun getLongData(key: String, default: Long = 0): Long = runBlocking {
83 | return@runBlocking dataStore.data.map {
84 | it[longPreferencesKey(key)] ?: default
85 | }.first()
86 | }
87 |
88 | /**
89 | * 取出String数据
90 | */
91 | private fun getStringData(key: String, default: String = ""): String = runBlocking {
92 | return@runBlocking dataStore.data.map {
93 | it[stringPreferencesKey(key)] ?: default
94 | }.first()
95 | }
96 |
97 | /**
98 | * 取出String set数据
99 | */
100 | private fun getStringSetData(key: String): Set =
101 | runBlocking {
102 | return@runBlocking dataStore.data.map {
103 | it[stringSetPreferencesKey(key)] ?: setOf()
104 | }.first() as Set
105 | }
106 |
107 | /**
108 | * 取出Boolean数据
109 | */
110 | private fun getBooleanData(key: String, default: Boolean = false): Boolean = runBlocking {
111 | return@runBlocking dataStore.data.map {
112 | it[booleanPreferencesKey(key)] ?: default
113 | }.first()
114 | }
115 |
116 | /**
117 | * 取出Float数据
118 | */
119 | private fun getFloatData(key: String, default: Float = 0.0f): Float = runBlocking {
120 | return@runBlocking dataStore.data.map {
121 | it[floatPreferencesKey(key)] ?: default
122 | }.first()
123 | }
124 |
125 | /**
126 | * 取出Double数据
127 | */
128 | private fun getDoubleData(key: String, default: Double = 0.00): Double = runBlocking {
129 | return@runBlocking dataStore.data.map {
130 | it[doublePreferencesKey(key)] ?: default
131 | }.first()
132 | }
133 |
134 | /**
135 | * 存数据
136 | */
137 | fun putData(key: String, value: T) {
138 | runBlocking {
139 | when (value) {
140 | is Int -> putIntData(key, value)
141 | is Long -> putLongData(key, value)
142 | is String -> putStringData(key, value)
143 | is Boolean -> putBooleanData(key, value)
144 | is Float -> putFloatData(key, value)
145 | is Double -> putDoubleData(key, value)
146 | is Set<*> -> putStringSetData(key, value)
147 | else -> throw IllegalArgumentException("This type cannot be saved to the Data Store")
148 | }
149 | }
150 | }
151 |
152 | fun getData(key: String, defaultValue: T): T {
153 | if (defaultValue == null) {
154 | return getStringData(key, "") as T
155 | }
156 | val data = when (defaultValue) {
157 | is Int -> getIntData(key, defaultValue)
158 | is Long -> getLongData(key, defaultValue)
159 | is String -> getStringData(key, defaultValue)
160 | is Boolean -> getBooleanData(key, defaultValue)
161 | is Float -> getFloatData(key, defaultValue)
162 | is Double -> getDoubleData(key, defaultValue)
163 | is Set<*> -> getStringSetData(key)
164 | else -> throw IllegalArgumentException("This type cannot be saved to the Data Store key $key defaultValue $defaultValue")
165 | }
166 | return data as T
167 | }
168 |
169 | /**
170 | * 清空数据
171 | */
172 | fun clearData() = runBlocking { dataStore.edit { it.clear() } }
173 | }
174 |
175 |
176 | class SettingsDataStore() : PreferenceDataStore() {
177 | override fun putString(key: String?, value: String?) {
178 | putData(key, value)
179 | }
180 |
181 | override fun putStringSet(
182 | key: String?,
183 | values: Set?
184 | ) {
185 | putData(key, values)
186 | }
187 |
188 | override fun putInt(key: String?, value: Int) {
189 | putData(key, value)
190 | }
191 |
192 | override fun putLong(key: String?, value: Long) {
193 | putData(key, value)
194 | }
195 |
196 | override fun putFloat(key: String?, value: Float) {
197 | putData(key, value)
198 | }
199 |
200 | override fun putBoolean(key: String?, value: Boolean) {
201 | putData(key, value)
202 | }
203 |
204 | override fun getString(key: String?, defValue: String?): String? {
205 | return getData(key, defValue)
206 | }
207 |
208 | override fun getStringSet(
209 | key: String?,
210 | defValues: Set?
211 | ): Set? {
212 | return getData(key, defValues)
213 | }
214 |
215 | override fun getInt(key: String?, defValue: Int): Int {
216 | return getData(key, defValue)
217 | }
218 |
219 | override fun getLong(key: String?, defValue: Long): Long {
220 | return getData(key, defValue)
221 | }
222 |
223 | override fun getFloat(key: String?, defValue: Float): Float {
224 | return getData(key, defValue)
225 | }
226 |
227 | override fun getBoolean(key: String?, defValue: Boolean): Boolean {
228 | return getData(key, defValue)
229 | }
230 |
231 | fun putData(key: String?, value: T) {
232 | if (key == null) {
233 | throw IllegalArgumentException("put data key couldn't null !!!")
234 | }
235 | DataStoreUtil.putData(key, value)
236 | }
237 |
238 | fun getData(key: String?, defaultValue: T): T {
239 | if (key == null) {
240 | throw IllegalArgumentException("get data key couldn't null !!!")
241 | }
242 | return DataStoreUtil.getData(key, defaultValue)
243 | }
244 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/viewmodel/OfflineFileViewModel.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.viewmodel
2 |
3 |
4 | import android.app.Application
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.setValue
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import com.elvishew.xlog.XLog
11 | import github.zerorooot.nap511.bean.FilesBean
12 | import github.zerorooot.nap511.bean.OfflineInfo
13 | import github.zerorooot.nap511.bean.OfflineTask
14 | import github.zerorooot.nap511.bean.QuotaBean
15 | import github.zerorooot.nap511.bean.TorrentFileBean
16 | import github.zerorooot.nap511.repository.FileRepository
17 | import github.zerorooot.nap511.util.App
18 | import github.zerorooot.nap511.util.ConfigKeyUtil
19 | import github.zerorooot.nap511.util.DataStoreUtil
20 | import github.zerorooot.nap511.util.DialogSwitchUtil
21 | import kotlinx.coroutines.flow.MutableStateFlow
22 | import kotlinx.coroutines.flow.asStateFlow
23 | import kotlinx.coroutines.launch
24 | import java.text.SimpleDateFormat
25 | import java.util.Locale
26 |
27 | class OfflineFileViewModel(private val cookie: String, private val application: Application) :
28 | ViewModel() {
29 | private val _isRefreshing = MutableStateFlow(false)
30 | var isRefreshing = _isRefreshing.asStateFlow()
31 |
32 | private val _offlineFile = MutableStateFlow(arrayListOf())
33 | var offlineFile = _offlineFile.asStateFlow()
34 |
35 | private val _offlineInfo = MutableStateFlow(OfflineInfo())
36 | var offlineInfo = _offlineInfo.asStateFlow()
37 |
38 | private val _quotaBean = MutableStateFlow(QuotaBean(1500, 1500))
39 | var quotaBean = _quotaBean.asStateFlow()
40 |
41 | lateinit var offlineTask: OfflineTask
42 |
43 | val dialogSwitchUtil = DialogSwitchUtil.getInstance()
44 |
45 |
46 | var torrentBean by mutableStateOf(TorrentFileBean())
47 | val torrentBeanCache = hashMapOf()
48 |
49 | private val fileRepository: FileRepository by lazy {
50 | FileRepository.getInstance(cookie)
51 | }
52 |
53 | fun getOfflineFileList() {
54 | if (_offlineFile.value.isNotEmpty()) {
55 | return
56 | }
57 | viewModelScope.launch {
58 | _isRefreshing.value = true
59 | // val uid = sharedPreferencesUtil.get(ConfigUtil.uid)!!
60 | val uid = App.uid
61 | val sign = fileRepository.getOfflineSign().sign
62 | _offlineInfo.value = fileRepository.getOfflineTaskList(uid, sign)
63 | setTaskInfo(_offlineInfo.value.tasks)
64 | _offlineFile.value = _offlineInfo.value.tasks
65 | _isRefreshing.value = false
66 | }
67 | }
68 |
69 | fun getTorrentTask(sha1: String) {
70 | //clear torrent bean
71 | torrentBean = TorrentFileBean()
72 | if (torrentBeanCache.contains(sha1)) {
73 | torrentBean = torrentBeanCache[sha1]!!
74 | return
75 | }
76 | viewModelScope.launch {
77 | val sign = fileRepository.getOfflineSign().sign
78 | val torrentTask = fileRepository.getOfflineTorrentTaskList(sha1, sign, App.uid)
79 | XLog.d("getTorrentTask $torrentTask")
80 | if (!torrentTask.state) {
81 | App.instance.toast(torrentTask.errorMessage)
82 | return@launch
83 | }
84 | torrentTask.fileSizeString = android.text.format.Formatter.formatFileSize(
85 | application, torrentTask.fileSize
86 | ) + " "
87 | // torrentTask.torrentFileListWeb.removeIf { i -> i.wanted == -1 }
88 | torrentTask.torrentFileListWeb.forEach { b ->
89 | b.sizeString = android.text.format.Formatter.formatFileSize(
90 | application, b.size
91 | ) + " "
92 | }
93 | torrentBeanCache.put(sha1, torrentTask)
94 | torrentBean = torrentTask
95 | }
96 | }
97 |
98 | fun addTorrentTask(infoHash: String, savePath: String, wanted: String) {
99 | viewModelScope.launch {
100 | val sign = fileRepository.getOfflineSign().sign
101 | val addTorrentTask = fileRepository.addOfflineTorrentTask(
102 | infoHash,
103 | wanted,
104 | savePath,
105 | App.uid,
106 | sign
107 | )
108 | val message = if (addTorrentTask.state) {
109 | "任务添加成功,文件已保存至 /云下载/${savePath}"
110 | } else {
111 | if (addTorrentTask.errorMsg.contains("请验证账号")) {
112 | //打开验证页面
113 | App.selectedItem = ConfigKeyUtil.VERIFY_MAGNET_LINK_ACCOUNT
114 | }
115 | "任务添加失败,${addTorrentTask.errorMsg}"
116 | }
117 | App.instance.toast(message)
118 | }
119 | }
120 |
121 | private fun setTaskInfo(tasks: ArrayList) {
122 | tasks.forEach { offlineTask ->
123 | offlineTask.timeString =
124 | SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(
125 | offlineTask.addTime * 1000
126 | )
127 | offlineTask.sizeString = android.text.format.Formatter.formatFileSize(
128 | application, if (offlineTask.size == -1L) 0L else offlineTask.size
129 | )
130 | offlineTask.percentString =
131 | if (offlineTask.status == -1) "❎下载失败" else "⬇${offlineTask.percentDone.toInt()}%"
132 | }
133 | }
134 |
135 | fun refresh() {
136 | _offlineFile.value.clear()
137 | getOfflineFileList()
138 | }
139 |
140 | fun clearFinish() {
141 | viewModelScope.launch {
142 | val clearFinish = fileRepository.clearOfflineFinish()
143 | val message = if (clearFinish.state) {
144 | "清除成功"
145 | } else {
146 | "清除失败,${clearFinish.errorMsg}"
147 | }
148 | App.instance.toast(message)
149 | }
150 | }
151 |
152 | fun clearError() {
153 | viewModelScope.launch {
154 | val clearError = fileRepository.clearOfflineError()
155 | val message = if (clearError.state) {
156 | "清除成功"
157 | } else {
158 | "清除失败,${clearError.errorMsg}"
159 | }
160 | App.instance.toast(message)
161 | }
162 | }
163 |
164 | fun quota() {
165 | viewModelScope.launch {
166 | _quotaBean.value = fileRepository.quota()
167 | }
168 | }
169 |
170 |
171 | /**
172 | * savepath:
173 | wp_path_id:currentCid
174 | url[0]:xxxxx
175 | url[1]:xxxxx
176 | uid:xxxx
177 | sign:xxxxxxx
178 | time:1675155957
179 | */
180 | fun addTask(list: List, currentCid: String) {
181 | viewModelScope.launch {
182 | fileRepository.addOfflineTask(list, currentCid)
183 | }
184 | }
185 |
186 | fun openOfflineDialog(index: Int) {
187 | dialogSwitchUtil.isOpenOfflineDialog = true
188 | offlineTask = _offlineFile.value[index]
189 | }
190 |
191 | fun closeOfflineDialog() {
192 | dialogSwitchUtil.isOpenOfflineDialog = false
193 | }
194 |
195 | fun delete(offlineTask: OfflineTask) {
196 | viewModelScope.launch {
197 | val map = hashMapOf("hash[0]" to offlineTask.infoHash)
198 | // map["uid"] = sharedPreferencesUtil.get(ConfigUtil.uid)!!
199 | map["uid"] = DataStoreUtil.getData(ConfigKeyUtil.UID, "")
200 | map["sign"] = fileRepository.getOfflineSign().sign
201 | map["time"] = (System.currentTimeMillis() / 1000).toString()
202 | val deleteTask = fileRepository.deleteOfflineTask(map)
203 | val message = if (deleteTask.state) {
204 | refresh()
205 | "删除成功"
206 | } else {
207 | "删除失败,${deleteTask.errorMsg}"
208 | }
209 | App.instance.toast(message)
210 | }
211 | }
212 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/zerorooot/nap511/service/FileService.kt:
--------------------------------------------------------------------------------
1 | package github.zerorooot.nap511.service
2 |
3 | import com.google.gson.JsonElement
4 | import com.google.gson.JsonObject
5 | import github.zerorooot.nap511.bean.*
6 | import github.zerorooot.nap511.util.App
7 | import okhttp3.Interceptor
8 | import okhttp3.MediaType.Companion.toMediaTypeOrNull
9 | import okhttp3.OkHttpClient
10 | import okhttp3.Protocol
11 | import okhttp3.RequestBody
12 | import okhttp3.Response
13 | import okhttp3.ResponseBody
14 | import okhttp3.ResponseBody.Companion.toResponseBody
15 | import retrofit2.Retrofit
16 | import retrofit2.converter.gson.GsonConverterFactory
17 | import retrofit2.http.*
18 |
19 | interface FileService {
20 | companion object {
21 | @Volatile
22 | private var fileService: FileService? = null
23 | fun getInstance(cookie: String): FileService {
24 | if (fileService == null) {
25 | fileService = Retrofit
26 | .Builder()
27 | .baseUrl("https://webapi.115.com/")
28 | .addConverterFactory(GsonConverterFactory.create())
29 | // .addConverterFactory(FileBeanConverterFactory.create())
30 | //add cookie
31 | .client(
32 | OkHttpClient().newBuilder()
33 | .addInterceptor(Interceptor { chain ->
34 | try {
35 | chain.proceed(
36 | chain.request().newBuilder()
37 | .addHeader("Cookie", cookie)
38 | .addHeader(
39 | "User-Agent",
40 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 115Browser/23.9.3.6"
41 | )
42 | .build()
43 | )
44 | } catch (e: Exception) {
45 | //防止java.net.SocketTimeoutException: timeout 错误
46 | Response.Builder()
47 | .request(chain.request())
48 | .protocol(Protocol.HTTP_1_1)
49 | .code(500)
50 | .message(e.message ?: "Network error")
51 | .body("".toResponseBody("text/plain".toMediaTypeOrNull()))
52 | .build()
53 | }
54 | })
55 | .build()
56 | )
57 | .build()
58 | .create(FileService::class.java)
59 | }
60 | return fileService!!
61 | }
62 | }
63 |
64 | // @GET("files")
65 | // suspend fun getFileList(
66 | // @Query("cid") cid: String,
67 | // @Query("show_dir") showDir: Int = 1,
68 | // @Query("aid") aid: Int = 1,
69 | // @Query("limit") limit: Int = 40
70 | // ): ArrayList
71 |
72 | @GET("files")
73 | suspend fun getFiles(
74 | @Query("cid") cid: String,
75 | @Query("show_dir") showDir: Int = 1,
76 | @Query("aid") aid: Int = 1,
77 | @Query("asc") asc: Int = 1,
78 | @Query("o") order: String = "file_name",
79 | @Query("limit") limit: Int = App.requestLimitCount
80 | ): FilesBean
81 |
82 | @GET("category/get")
83 | suspend fun getFileInfo(@Query("cid") cid: String): FileInfo
84 |
85 | @GET("files/image")
86 | suspend fun image(@Query("pickcode") pickCode: String, @Query("_") current: Long): ImageDate
87 |
88 | @GET("files/search")
89 | suspend fun search(
90 | @Query("cid") cid: String,
91 | @Query("search_value") searchValue: String,
92 | @Query("aid") aid: Int = 1,
93 | @Query("offset") asc: Int = 0,
94 | @Query("limit") limit: Int = 400
95 | ): FilesBean
96 |
97 |
98 | /**
99 | *
100 | user_order:file_size
101 | file_id:2573609193685653011
102 | user_asc:1
103 | fc_mix:0
104 | */
105 | @POST("files/order")
106 | @FormUrlEncoded
107 | suspend fun order(@FieldMap body: Map): BaseReturnMessage
108 |
109 | /**
110 | *
111 | pid:currentCid
112 | move_proid:xxxx
113 | fid[0]:xxx
114 | fid[1]:xxxx
115 | */
116 | @FormUrlEncoded
117 | @POST("files/move")
118 | suspend fun move(@FieldMap body: Map): BaseReturnMessage
119 |
120 | @FormUrlEncoded
121 | @POST("rb/delete")
122 | suspend fun delete(
123 | @Field("pid") pid: String,
124 | @Field("fid[0]") fid: String,
125 | @Field("ignore_warn") ignoreWarn: Int = 1
126 | ): BaseReturnMessage
127 |
128 | @FormUrlEncoded
129 | @POST("rb/delete")
130 | suspend fun deleteMultiple(
131 | @FieldMap data: Map
132 | ): BaseReturnMessage
133 |
134 | @FormUrlEncoded
135 | @POST("rb/revert")
136 | suspend fun revert(
137 | @Field("rid[0]") rid: String
138 | ): BaseReturnMessage
139 |
140 |
141 | @GET("rb")
142 | suspend fun recycleList(
143 | @Query("aid") aid: String = "7",
144 | @Query("cid") cid: String = "0",
145 | @Query("offset") offset: String = "0",
146 | @Query("limit") limit: String = "100"
147 | ): RecycleInfo
148 |
149 | @FormUrlEncoded
150 | @POST("rb/clean")
151 | suspend fun recycleClean(
152 | @Field("rid[0]") rid: String,
153 | @Field("password") password: String
154 | ): BaseReturnMessage
155 |
156 | @FormUrlEncoded
157 | @POST("rb/clean")
158 | suspend fun recycleCleanAll(
159 | @Field("password") password: String
160 | ): BaseReturnMessage
161 |
162 | /**
163 | * cid 当前目录的cid
164 | */
165 | @FormUrlEncoded
166 | @POST("files/add")
167 | suspend fun createFolder(
168 | @Field("pid") pid: String,
169 | @Field("cname") folderName: String
170 | ): CreateFolderMessage
171 |
172 | /**
173 | * files_new_name[fid]=newName
174 | */
175 | @POST("files/batch_rename")
176 | suspend fun rename(@Body renameBean: RequestBody): BaseReturnMessage
177 |
178 | @FormUrlEncoded
179 | @POST("offine/downpath")
180 | suspend fun setDownloadPath(@Field("file_id") cid: String): BaseReturnMessage
181 |
182 |
183 | /**
184 | * 获取剩余空间
185 | */
186 | @GET("files/index_info")
187 | suspend fun remainingSpace(@Query("count_space_nums") countSpaceNum: Int = 1): JsonObject
188 |
189 | @GET("files/extract_info")
190 | suspend fun getZipListFile(
191 | @Query("pick_code") pickCode: String,
192 | @Query("file_name") fileName: String = "",
193 | @Query("paths") paths: String = "文件",
194 | @Query("page_count") pageCount: String = "999",
195 | ): JsonObject
196 |
197 |
198 | @FormUrlEncoded
199 | @POST("files/push_extract")
200 | suspend fun decryptZip(
201 | @Field("pick_code") pickCode: String,
202 | @Field("secret") secret: String
203 | ): JsonObject
204 |
205 | /**
206 | * {"state":true,"message":"","code":"","data":{"extract_status":{"unzip_status":4,"progress":100}}}
207 | */
208 | @GET("files/push_extract")
209 | suspend fun getDecryptZipProcess(
210 | @Query("pick_code") pickCode: String
211 | ): JsonObject
212 |
213 | /**
214 | * {"state":true,"message":"","code":"","data":{"unzip_status":1}}
215 | */
216 | @FormUrlEncoded
217 | @POST("files/push_extract")
218 | suspend fun checkDecryptZip(
219 | @Field("pick_code") pickCode: String
220 | ): JsonObject
221 |
222 | /**
223 | * map extract_file[] -> xxxx
224 | * extract_file[] -> xxxx
225 | * extract_dir[] -> xxx
226 | * extract_dir[] -> xxx
227 | */
228 | @FormUrlEncoded
229 | @POST("files/add_extract_file")
230 | suspend fun unzipFile(
231 | @Field("pick_code") pickCode: String,
232 | @Field("to_pid") pid: String,
233 | @Field("extract_file[]") files: List?,
234 | @Field("extract_dir[]") dirs: List?,
235 | @Field("paths") paths: String = "文件"
236 | ): JsonElement
237 |
238 | /**
239 | * {"state":true,"message":"","code":"","data":{"extract_id":"id","to_pid":"pid","percent":100}}
240 | */
241 | @GET("files/add_extract_file")
242 | suspend fun unzipFileProcess(
243 | @Query("extract_id") extractId: Long,
244 | ): JsonObject
245 | }
246 |
247 |
--------------------------------------------------------------------------------