├── 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 | 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 | 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 | 8 | 9 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------