diff --git a/.gitignore b/.gitignore
index 0fafa35..6d2bf6b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
/local.properties
/.idea
.idea
+.idea/
.DS_Store
/build
/captures
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 6195b36..e0da3ee 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -3,15 +3,19 @@
+
+
+
+
@@ -35,6 +39,7 @@
+
diff --git a/README.md b/README.md
index 448e7db..97f4f6a 100644
--- a/README.md
+++ b/README.md
@@ -10,17 +10,18 @@
2. 网盘文件相关(剪切、删除、重命名、新建文件夹、多选、回收站、获取下载链接、搜索、~~小文本创建、在线解压~~)
3. 离线文件相关(离线列表、点击跳转到网盘文件夹、查看视频文件、删除、清空)
4. 查看(~~小文本、音频~~、照片、视频)
-5. 离线(磁力链接离线下载、115sha1导出、~~种子离线下载~~)
+5. 离线(磁力链接离线下载、115sha1导出、种子离线下载)
6. 自定义(单次文件请求数量、默认离线位置)
7. 其他(磁力跳转、~~支持大屏~~)
不支持**文件的上传与下载**、**两步验证**、**安全密钥**
# 下载
-尝鲜版
+## 尝鲜版
+
需登录github账户:https://github.com/zerorooot/nap511/actions/workflows/generate-apk-release.yml
-无需登录:https://nightly.link/zerorooot/nap511/workflows/generate-apk-release/main?preview
+无需登录github账户:https://nightly.link/zerorooot/nap511/workflows/generate-apk-release/main?preview
-稳定版
+## 稳定版
https://github.com/zerorooot/nap511/releases
\ No newline at end of file
diff --git a/app/src/main/java/github/zerorooot/nap511/MainActivity.kt b/app/src/main/java/github/zerorooot/nap511/MainActivity.kt
index 7913a6a..f3ec321 100644
--- a/app/src/main/java/github/zerorooot/nap511/MainActivity.kt
+++ b/app/src/main/java/github/zerorooot/nap511/MainActivity.kt
@@ -1,9 +1,6 @@
package github.zerorooot.nap511
import android.annotation.SuppressLint
-import android.app.AppOpsManager
-import android.content.Context
-import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
@@ -18,12 +15,15 @@ import androidx.compose.ui.unit.dp
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkInfo
import androidx.work.WorkManager
+import androidx.work.WorkQuery
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import github.zerorooot.nap511.activity.OfflineTaskWorker
-import github.zerorooot.nap511.bean.LoginBean
+import github.zerorooot.nap511.bean.TorrentFileBean
+import github.zerorooot.nap511.bean.TorrentFileListWeb
import github.zerorooot.nap511.factory.CookieViewModelFactory
import github.zerorooot.nap511.screen.*
import github.zerorooot.nap511.ui.theme.Nap511Theme
@@ -34,11 +34,6 @@ import github.zerorooot.nap511.viewmodel.FileViewModel
import github.zerorooot.nap511.viewmodel.OfflineFileViewModel
import github.zerorooot.nap511.viewmodel.RecycleViewModel
import kotlinx.coroutines.launch
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import java.lang.reflect.Field
-import java.lang.reflect.InvocationTargetException
-import java.lang.reflect.Method
import kotlin.concurrent.thread
@@ -57,11 +52,12 @@ class MainActivity : ComponentActivity() {
Login()
return@Surface
}
+ //初始化
Init(cookie)
//允许通知, 方便离线下载交互 OfflineTaskActivity
- if (!isNotificationEnabled(this)) {
+ if (!App.instance.isNotificationEnabled(this)) {
App.instance.toast("检测到未开启通知权限,为保证交互效果,建议开启")
- goToNotificationSetting(this)
+ App.instance.goToNotificationSetting(this)
}
//直接添加磁力,但提示请验证账号;跳转到验证账号界面
if (intent.action == "jump") {
@@ -72,60 +68,6 @@ class MainActivity : ComponentActivity() {
}
}
- /**
- * 判断允许通知,是否已经授权
- * 返回值为true时,通知栏打开,false未打开。
- * @param context 上下文
- */
- private fun isNotificationEnabled(context: Context): Boolean {
- val CHECK_OP_NO_THROW = "checkOpNoThrow"
- val OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"
- val mAppOps = context.getSystemService(APP_OPS_SERVICE) as AppOpsManager
- val appInfo = context.applicationInfo
- val pkg = context.applicationContext.packageName
- val uid = appInfo.uid
- val appOpsClass: Class<*>?
- /* Context.APP_OPS_MANAGER */try {
- appOpsClass = Class.forName(AppOpsManager::class.java.name)
- val checkOpNoThrowMethod: Method = appOpsClass.getMethod(
- CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
- String::class.java
- )
- val opPostNotificationValue: Field = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION)
- val value = opPostNotificationValue.get(Int::class.java) as Int
- return checkOpNoThrowMethod.invoke(
- mAppOps,
- value,
- uid,
- pkg
- ) as Int == AppOpsManager.MODE_ALLOWED
- } catch (e: ClassNotFoundException) {
- e.printStackTrace()
- } catch (e: NoSuchMethodException) {
- e.printStackTrace()
- } catch (e: NoSuchFieldException) {
- e.printStackTrace()
- } catch (e: InvocationTargetException) {
- e.printStackTrace()
- } catch (e: IllegalAccessException) {
- e.printStackTrace()
- }
- return false
- }
-
- /**
- * 跳转到app的设置界面--开启通知
- * @param context
- */
- private fun goToNotificationSetting(context: Context) {
- val intent = Intent()
- // android 8.0引导
- intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS")
- intent.putExtra("android.provider.extra.APP_PACKAGE", context.packageName)
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- context.startActivity(intent)
- }
-
@Composable
private fun Init(cookie: String) {
val fileViewModel by viewModels {
@@ -164,13 +106,23 @@ class MainActivity : ComponentActivity() {
/**
* 检测添加的离线链接。防止因为种种原因,app添加离线链接,但链接没有上传到115
* 后台添加离线链接的代码在OfflineTaskActivity
+ *
*/
private fun checkOfflineTask(cookie: String) {
- val size =
- WorkManager.getInstance(applicationContext).getWorkInfosByTag("addOfflineTaskByTime")
- .get().size + WorkManager.getInstance(applicationContext)
- .getWorkInfosByTag("addOfflineTaskByCount").get().size
- println("checkOfflineTask workManager size $size")
+ val workQuery = WorkQuery.Builder.fromStates(
+ listOf(
+ WorkInfo.State.ENQUEUED,
+ WorkInfo.State.RUNNING,
+ WorkInfo.State.SUCCEEDED,
+ WorkInfo.State.FAILED,
+ WorkInfo.State.BLOCKED,
+ WorkInfo.State.CANCELLED
+ )
+ ).build()
+ val workInfos: List =
+ WorkManager.getInstance(applicationContext).getWorkInfos(workQuery).get()
+ val size = workInfos.size
+ println("checkOfflineTask workManager size $size workInfos $workInfos")
if (size != 0) {
return
}
@@ -249,7 +201,7 @@ class MainActivity : ComponentActivity() {
content = {
when (App.selectedItem) {
"登录" -> Login()
- "我的文件" -> MyFileScreen(fileViewModel)
+ "我的文件" -> MyFileScreen(offlineFileViewModel, fileViewModel)
"离线下载" -> OfflineDownloadScreen(offlineFileViewModel, fileViewModel)
"离线列表" -> OfflineFileScreen(offlineFileViewModel, fileViewModel)
"网页版" -> WebViewScreen()
@@ -260,6 +212,7 @@ class MainActivity : ComponentActivity() {
"https://captchaapi.115.com/?ac=security_code&type=web&cb=Close911_" + System.currentTimeMillis()
CaptchaWebViewScreen()
}
+
"验证video账号" -> {
CaptchaVideoWebViewScreen()
}
@@ -311,11 +264,15 @@ class MainActivity : ComponentActivity() {
}
@Composable
- private fun MyFileScreen(fileViewModel: FileViewModel) {
+ private fun MyFileScreen(
+ offlineFileViewModel: OfflineFileViewModel,
+ fileViewModel: FileViewModel
+ ) {
fileViewModel.init()
FileScreen(
fileViewModel,
+ offlineFileViewModel,
appBarClick(fileViewModel),
)
}
diff --git a/app/src/main/java/github/zerorooot/nap511/activity/OfflineTaskActivity.kt b/app/src/main/java/github/zerorooot/nap511/activity/OfflineTaskActivity.kt
index 7676f03..ab2578a 100644
--- a/app/src/main/java/github/zerorooot/nap511/activity/OfflineTaskActivity.kt
+++ b/app/src/main/java/github/zerorooot/nap511/activity/OfflineTaskActivity.kt
@@ -67,14 +67,14 @@ class OfflineTaskActivity : Activity() {
.split("\n")
.filter { i -> i != "" && i != " " }
.toSet()
- .toMutableList()
+ .toMutableSet()
//添加所有
currentOfflineTaskList.addAll(urlList)
//离线任务缓存方式,true为x分钟后统一下载,false为集满后统一下载
if (DataStoreUtil.getData(ConfigUtil.offlineMethod, true)) {
- addOfflineTaskByTime(currentOfflineTaskList)
+ addOfflineTaskByTime(currentOfflineTaskList.toList())
} else {
- addOfflineTaskByCount(currentOfflineTaskList)
+ addOfflineTaskByCount(currentOfflineTaskList.toList())
}
} else {
App.instance.toast("仅支持以http、ftp、magnet、ed2k开头的链接")
@@ -177,7 +177,7 @@ class OfflineTaskWorker(
""
)
}
- println(message)
+// println(message)
toast(message, a)
return Result.success(addTaskData);
}
diff --git a/app/src/main/java/github/zerorooot/nap511/activity/TorrentTaskActivity.kt b/app/src/main/java/github/zerorooot/nap511/activity/TorrentTaskActivity.kt
index 8a59c1f..b57b815 100644
--- a/app/src/main/java/github/zerorooot/nap511/activity/TorrentTaskActivity.kt
+++ b/app/src/main/java/github/zerorooot/nap511/activity/TorrentTaskActivity.kt
@@ -32,7 +32,7 @@ class TorrentTaskActivity : Activity() {
super.onCreate(savedInstanceState)
if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
val torrentFile = fileFromContentUri(this, intent.data!!)
- val uid = DataStoreUtil.getData(ConfigUtil.uid, "0")
+ val uid = App.uid
val defaultOfflineCid = DataStoreUtil.getData(ConfigUtil.defaultOfflineCid, "0")
initUpload(torrentFile, App.cookie, uid, defaultOfflineCid)
}
diff --git a/app/src/main/java/github/zerorooot/nap511/bean/Bean.kt b/app/src/main/java/github/zerorooot/nap511/bean/Bean.kt
index 6805089..e08a5a7 100644
--- a/app/src/main/java/github/zerorooot/nap511/bean/Bean.kt
+++ b/app/src/main/java/github/zerorooot/nap511/bean/Bean.kt
@@ -289,3 +289,47 @@ data class InitUploadBean(
val signature: String,
val callback: String
)
+
+/**
+ * {
+ * "state": true,
+ * "errno": 0,
+ * "errtype": "suc",
+ * "errcode": 0,
+ * "file_size": 70966705837,
+ * "torrent_name": "name",
+ * "file_count": 28,
+ * "info_hash": "hash",
+ * "torrent_filelist_web": [
+ * {
+ * "size": 3902418,
+ * "path": "预览图/2021_04_24_07_37_IMG_1379.JPG",
+ * "wanted": 1
+ * }
+ * ]
+ * }
+ */
+data class TorrentFileBean(
+ var state: Boolean = false,
+ var errno: Long = 0,
+ var errtype: String = "suc",
+ var errcode: Long = 0,
+ @SerializedName("file_size")
+ var fileSize: Long = 0,
+ var fileSizeString: String = "",
+ @SerializedName("torrent_name")
+ var torrentName: String = "",
+ @SerializedName("file_count")
+ var fileCount: Int = 0,
+ @SerializedName("info_hash")
+ var infoHash: String = "",
+ @SerializedName("torrent_filelist_web")
+ var torrentFileListWeb: ArrayList = arrayListOf(),
+)
+
+data class TorrentFileListWeb(
+ var size: Long = 0,
+ var sizeString: String = "",
+ var path: String = "",
+ val wanted: Int = -1,
+)
\ No newline at end of file
diff --git a/app/src/main/java/github/zerorooot/nap511/screen/AlertDialog.kt b/app/src/main/java/github/zerorooot/nap511/screen/AlertDialog.kt
index 4b7676f..7b0e047 100644
--- a/app/src/main/java/github/zerorooot/nap511/screen/AlertDialog.kt
+++ b/app/src/main/java/github/zerorooot/nap511/screen/AlertDialog.kt
@@ -2,15 +2,42 @@ package github.zerorooot.nap511.screen
import android.app.Activity
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.outlined.CheckBox
+import androidx.compose.material.icons.outlined.CheckBoxOutlineBlank
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateMapOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -26,9 +53,14 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties
+import com.google.gson.Gson
import com.google.gson.JsonObject
import github.zerorooot.nap511.R
+import github.zerorooot.nap511.bean.TorrentFileBean
+import github.zerorooot.nap511.bean.TorrentFileListWeb
+import github.zerorooot.nap511.screenitem.AutoSizableTextField
import github.zerorooot.nap511.util.App
import github.zerorooot.nap511.util.ConfigUtil
import github.zerorooot.nap511.viewmodel.FileViewModel
@@ -43,6 +75,17 @@ fun CreateFolderDialog(fileViewModel: FileViewModel, enter: (String) -> Unit) {
}
}
+@Composable
+fun CreateSelectTorrentFileDialog(
+ offlineFileViewModel: OfflineFileViewModel,
+ enter: (TorrentFileBean, Map) -> Unit
+) {
+ if (offlineFileViewModel.isOpenCreateSelectTorrentFileDialog) {
+ val torrentBean by offlineFileViewModel.torrentBean.collectAsState()
+ SelectTorrentFileDialog(torrentBean, enter)
+ }
+}
+
@Composable
fun SearchDialog(fileViewModel: FileViewModel, enter: (String) -> Unit) {
if (fileViewModel.isOpenSearchDialog) {
@@ -160,7 +203,7 @@ fun FileOrderDialog(fileViewModel: FileViewModel, enter: (String) -> Unit) {
}
}
-@OptIn(ExperimentalMaterial3Api::class)
+
@Composable
fun Aria2Dialog(fileViewModel: FileViewModel, context: String, enter: (String) -> Unit) {
if (!fileViewModel.isOpenAria2Dialog) {
@@ -312,7 +355,149 @@ private fun RadioButtonDialog(
}
-@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun SelectTorrentFileDialog(
+ torrentFileBean: TorrentFileBean,
+ enter: (torrentFileBean: TorrentFileBean, Map) -> Unit
+) {
+ val listState = rememberLazyListState()
+ val selectMap = remember { mutableStateMapOf() }
+ var sumSize: Long = 0
+ val isSelectedItem: (Int) -> Boolean = { selectMap.containsKey(it) }
+ val onChangeState: (Int, TorrentFileListWeb) -> Unit = { i: Int, s: TorrentFileListWeb ->
+ sumSize = 0
+ if (selectMap.containsKey(i)) {
+ selectMap.remove(i)
+ } else {
+ selectMap[i] = s
+ }
+ selectMap.values.forEach { b -> sumSize += b.size }
+ }
+
+ AlertDialog(
+ onDismissRequest = {
+ selectMap.clear()
+ enter.invoke(torrentFileBean, selectMap)
+ },
+ confirmButton = {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.offset(y = (-20).dp)
+ ) {
+ TextButton(
+ onClick = {
+ enter.invoke(torrentFileBean, selectMap)
+ },
+ ) {
+ Text(text = "下载")
+ }
+ TextButton(
+ onClick = {
+ selectMap.clear()
+ torrentFileBean.torrentFileListWeb.forEachIndexed { index, torrentFileListWeb ->
+ if (torrentFileListWeb.wanted == 1) {
+ selectMap[index] = torrentFileListWeb
+ }
+ }
+ sumSize = 0
+ selectMap.values.forEach { b -> sumSize += b.size }
+ },
+ ) {
+ Text(text = "全选")
+ }
+ TextButton(
+ onClick = {
+ torrentFileBean.torrentFileListWeb.forEachIndexed { index, torrentFileListWeb ->
+ if (torrentFileListWeb.wanted == 1) {
+ if (selectMap.containsKey(index)) {
+ selectMap.remove(index)
+ } else {
+ selectMap[index] = torrentFileListWeb
+ }
+ }
+ }
+ sumSize = 0
+ selectMap.values.forEach { b -> sumSize += b.size }
+ },
+ ) {
+ Text(text = "反选")
+ }
+ TextButton(
+ onClick = {
+ selectMap.clear()
+ enter.invoke(torrentFileBean, selectMap)
+ },
+ ) {
+ Text(text = "取消")
+ }
+ }
+ }, title = { Text(text = "选择要下载的文件") }, text = {
+ Column() {
+ AutoSizableTextField(
+ value = "已经选择${selectMap.size}/${torrentFileBean.fileCount}个,总计:${
+ android.text.format.Formatter.formatFileSize(App.instance, sumSize)
+ }\n" + "共${torrentFileBean.fileCount}个文件,总计:${torrentFileBean.fileSizeString}",
+ minFontSize = 30.sp,
+ maxLines = 2
+ )
+ LazyColumn(modifier = Modifier.padding(8.dp), state = listState) {
+ itemsIndexed(items = torrentFileBean.torrentFileListWeb, key = { _, item ->
+ item.hashCode()
+ }) { index, item ->
+ if (item.wanted == 1) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .selectable(
+ selected = isSelectedItem(index), onClick = {
+ onChangeState(index, item)
+// println("select $selectMap")
+ }, role = Role.RadioButton
+ )
+ .padding(8.dp)
+ ) {
+ Icon(
+ modifier = Modifier.padding(end = 16.dp),
+ imageVector = if (isSelectedItem(index)) {
+ Icons.Outlined.CheckBox
+ } else {
+ Icons.Outlined.CheckBoxOutlineBlank
+ },
+ contentDescription = null,
+ tint = Color.Magenta
+ )
+ AutoSizableTextField(
+ value = item.path,
+ modifier = Modifier.weight(1f),
+ minFontSize = 30.sp,
+ maxLines = 2
+ )
+ Text(
+ text = item.sizeString, modifier = Modifier.weight(0.5f),
+ )
+ }
+ }
+ }
+ }
+ }
+ })
+
+}
+
+@Composable
+@Preview
+fun SelectTorrentFileDialogPreview() {
+ val torrentFileBean = Gson().fromJson(
+ "{\"state\":true,\"errno\":0,\"fileSizeString\":123G,\"errtype\":\"suc\",\"errcode\":0,\"file_size\":70966705837,\"torrent_name\":\"name\",\"file_count\":28,\"info_hash\":\"hash\",\"torrent_filelist_web\":[{\"size\":12312,\"path\":\"预览图/0.JPG\",\"wanted\":1},{\"size\":123443242,\"path\":\"预览图/123123132132131312313123123/1.JPG\",\"wanted\":1},{\"size\":3902418,\"path\":\"预览图/2.JPG\",\"wanted\":1},{\"size\":321231321,\"path\":\"预览图/3.JPG\",\"wanted\":1},{\"size\":312321321,\"path\":\"预览图/4.JPG\",\"wanted\":-1}]}",
+ TorrentFileBean::class.java
+ )
+
+ SelectTorrentFileDialog(torrentFileBean) { a: TorrentFileBean, m: Map ->
+
+ }
+
+}
+
@Composable
fun InfoDialog(
onDismissRequest: () -> Unit,
diff --git a/app/src/main/java/github/zerorooot/nap511/screen/FileScreen.kt b/app/src/main/java/github/zerorooot/nap511/screen/FileScreen.kt
index 237cf20..d10cc93 100644
--- a/app/src/main/java/github/zerorooot/nap511/screen/FileScreen.kt
+++ b/app/src/main/java/github/zerorooot/nap511/screen/FileScreen.kt
@@ -60,6 +60,7 @@ import github.zerorooot.nap511.util.App
import github.zerorooot.nap511.util.ConfigUtil
import github.zerorooot.nap511.util.DataStoreUtil
import github.zerorooot.nap511.viewmodel.FileViewModel
+import github.zerorooot.nap511.viewmodel.OfflineFileViewModel
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
@@ -73,14 +74,15 @@ import kotlin.math.log
"UnrememberedMutableState"
)
@OptIn(
- ExperimentalAnimationApi::class,
ExperimentalFoundationApi::class,
ExperimentalMaterial3Api::class,
ExperimentalMaterialApi::class
)
@Composable
fun FileScreen(
- fileViewModel: FileViewModel, appBarOnClick: (String) -> Unit
+ fileViewModel: FileViewModel,
+ offlineFileViewModel: OfflineFileViewModel,
+ appBarOnClick: (String) -> Unit
) {
val fileBeanList = fileViewModel.fileBeanList
val path by fileViewModel.currentPath.collectAsState()
@@ -94,7 +96,7 @@ fun FileScreen(
val activity = LocalContext.current as Activity
- CreateDialogs(fileViewModel)
+ CreateDialogs(fileViewModel, offlineFileViewModel)
val itemOnLongClick = { i: Int ->
@@ -168,7 +170,7 @@ fun FileScreen(
//加载文件
fileViewModel.getFiles(fileBean.categoryId)
}
-
+ //打开视频
if (fileBean.isVideo == 1) {
val intent = Intent(activity, VideoActivity::class.java)
intent.putExtra("cookie", App.cookie)
@@ -176,6 +178,7 @@ fun FileScreen(
intent.putExtra("pick_code", fileBean.pickCode)
activity.startActivity(intent)
}
+ //打开图片
if (fileBean.photoThumb != "") {
//具体实现在MainActivity#MyNavigationDrawer()
val photoFileBeanList =
@@ -185,6 +188,11 @@ fun FileScreen(
fileViewModel.photoIndexOf = photoFileBeanList.indexOf(fileBean)
App.selectedItem = "photo"
}
+ // 打开种子文件
+ if (fileBean.fileIco == R.drawable.torrent) {
+ offlineFileViewModel.isOpenCreateSelectTorrentFileDialog = true
+ offlineFileViewModel.getTorrentTask(fileBean.sha1)
+ }
//滚动到当前目录
@@ -306,7 +314,7 @@ fun FileScreen(
item,
index,
fileViewModel.clickMap.getOrDefault(path, -1),
- Modifier.animateItemPlacement(),
+ Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
myItemOnClick,
itemOnLongClick,
menuOnClick
@@ -324,23 +332,27 @@ fun FileScreen(
@ExperimentalMaterial3Api
@Composable
-fun CreateDialogs(fileViewModel: FileViewModel) {
+fun CreateDialogs(fileViewModel: FileViewModel, offlineFileViewModel: OfflineFileViewModel) {
val context = LocalContext.current
+ //新建文件夹
CreateFolderDialog(fileViewModel) {
if (it != "") {
fileViewModel.createFolder(it)
}
fileViewModel.isOpenCreateFolderDialog = false
}
+ //重命名
RenameFileDialog(fileViewModel) {
if (it != "") {
fileViewModel.rename(it)
}
fileViewModel.isOpenRenameFileDialog = false
}
+ //文件信息
FileInfoDialog(fileViewModel) {
fileViewModel.isOpenFileInfoDialog = false
}
+ //文件排序
FileOrderDialog(fileViewModel = fileViewModel) {
fileViewModel.isOpenFileOrderDialog = false
if (it.contains("视频时间")) {
@@ -363,7 +375,7 @@ fun CreateDialogs(fileViewModel: FileViewModel) {
}
}
-
+//aria2
Aria2Dialog(
fileViewModel = fileViewModel, context = DataStoreUtil.getData(
ConfigUtil.aria2Url, ConfigUtil.aria2UrldefValue
@@ -377,13 +389,21 @@ fun CreateDialogs(fileViewModel: FileViewModel) {
thread { checkAria2(aria2Url, aria2Token, context) }
}
}
-
+//搜索
SearchDialog(fileViewModel) {
if (it != "") {
fileViewModel.search(it)
}
fileViewModel.isOpenSearchDialog = false
}
+ CreateSelectTorrentFileDialog(offlineFileViewModel) { torrentFileBean, map ->
+ offlineFileViewModel.isOpenCreateSelectTorrentFileDialog = false
+ if (map.isEmpty()) {
+ return@CreateSelectTorrentFileDialog
+ }
+ offlineFileViewModel.addTorrentTask(torrentFileBean, map.keys.joinToString(separator = ","))
+ }
+
}
/**
diff --git a/app/src/main/java/github/zerorooot/nap511/service/OfflineService.kt b/app/src/main/java/github/zerorooot/nap511/service/OfflineService.kt
index dadf41c..9aa56e9 100644
--- a/app/src/main/java/github/zerorooot/nap511/service/OfflineService.kt
+++ b/app/src/main/java/github/zerorooot/nap511/service/OfflineService.kt
@@ -52,6 +52,29 @@ interface OfflineService {
@Field("time") time: Long = System.currentTimeMillis() / 1000
): OfflineInfo
+ @FormUrlEncoded
+ @POST("web/lixian/?ct=lixian&ac=torrent")
+ suspend fun getTorrentTaskList(
+ @Field("sha1") sha1: String = "",
+ @Field("sign") sign: String = "",
+ @Field("uid") uid: String = "",
+ @Field("time") time: Long = System.currentTimeMillis() / 1000
+ ): TorrentFileBean
+
+ @FormUrlEncoded
+ @POST("web/lixian/?ct=lixian&ac=add_task_bt")
+ suspend fun addTorrentTask(
+ @Field("info_hash") infoHash: String = "",
+ //0,4,6
+ @Field("wanted") wanted: String = "",
+ //torrent name
+ @Field("savepath") savePath: String = "",
+ @Field("uid") uid: String = "",
+ @Field("sign") sign: String = "",
+ @Field("time") time: Long = System.currentTimeMillis() / 1000
+ ): BaseReturnMessage
+
+
/**
* hash[0]:xxxxxxx
* uid
@@ -66,8 +89,9 @@ interface OfflineService {
@FormUrlEncoded
@POST("web/lixian/?ct=lixian&ac=task_clear")
- suspend fun clearFinish(@Field("flag") flag: String = "0"):BaseReturnMessage
+ suspend fun clearFinish(@Field("flag") flag: String = "0"): BaseReturnMessage
+
@FormUrlEncoded
@POST("web/lixian/?ct=lixian&ac=task_clear")
- suspend fun clearError(@Field("flag") flag: String = "2"):BaseReturnMessage
+ suspend fun clearError(@Field("flag") flag: String = "2"): BaseReturnMessage
}
\ No newline at end of file
diff --git a/app/src/main/java/github/zerorooot/nap511/util/App.kt b/app/src/main/java/github/zerorooot/nap511/util/App.kt
index 099b092..0aa00b7 100644
--- a/app/src/main/java/github/zerorooot/nap511/util/App.kt
+++ b/app/src/main/java/github/zerorooot/nap511/util/App.kt
@@ -1,6 +1,9 @@
package github.zerorooot.nap511.util
+import android.app.AppOpsManager
import android.app.Application
+import android.content.Context
+import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.widget.Toast
@@ -14,11 +17,15 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
+import java.lang.reflect.Field
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
class App : Application() {
companion object {
lateinit var instance: App
var cookie by mutableStateOf("")
+ var uid by mutableStateOf("0")
//页面导航
var selectedItem by mutableStateOf("我的文件")
@@ -41,6 +48,7 @@ class App : Application() {
super.onCreate()
instance = this
cookie = DataStoreUtil.getData(ConfigUtil.cookie, "")
+ uid = DataStoreUtil.getData(ConfigUtil.uid, "0")
requestLimitCount = DataStoreUtil.getData(ConfigUtil.requestLimitCount, "100").toInt()
}
@@ -58,6 +66,7 @@ class App : Application() {
}
}
+
fun checkLogin(cookie: String): String {
val url =
"https://passportapi.115.com/app/1.0/web/1.0/check/sso?_${System.currentTimeMillis() / 1000}"
@@ -81,6 +90,60 @@ class App : Application() {
return uid
}
+ /**
+ * 判断允许通知,是否已经授权
+ * 返回值为true时,通知栏打开,false未打开。
+ * @param context 上下文
+ */
+ fun isNotificationEnabled(context: Context): Boolean {
+ val CHECK_OP_NO_THROW = "checkOpNoThrow"
+ val OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"
+ val mAppOps = context.getSystemService(APP_OPS_SERVICE) as AppOpsManager
+ val appInfo = context.applicationInfo
+ val pkg = context.applicationContext.packageName
+ val uid = appInfo.uid
+ val appOpsClass: Class<*>?
+ /* Context.APP_OPS_MANAGER */try {
+ appOpsClass = Class.forName(AppOpsManager::class.java.name)
+ val checkOpNoThrowMethod: Method = appOpsClass.getMethod(
+ CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
+ String::class.java
+ )
+ val opPostNotificationValue: Field = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION)
+ val value = opPostNotificationValue.get(Int::class.java) as Int
+ return checkOpNoThrowMethod.invoke(
+ mAppOps,
+ value,
+ uid,
+ pkg
+ ) as Int == AppOpsManager.MODE_ALLOWED
+ } catch (e: ClassNotFoundException) {
+ e.printStackTrace()
+ } catch (e: NoSuchMethodException) {
+ e.printStackTrace()
+ } catch (e: NoSuchFieldException) {
+ e.printStackTrace()
+ } catch (e: InvocationTargetException) {
+ e.printStackTrace()
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ }
+ return false
+ }
+
+ /**
+ * 跳转到app的设置界面--开启通知
+ * @param context
+ */
+ fun goToNotificationSetting(context: Context) {
+ val intent = Intent()
+ // android 8.0引导
+ intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS")
+ intent.putExtra("android.provider.extra.APP_PACKAGE", context.packageName)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+
fun closeDrawerState() {
if (isScopeInitialized()) {
scope.launch {
diff --git a/app/src/main/java/github/zerorooot/nap511/viewmodel/FileViewModel.kt b/app/src/main/java/github/zerorooot/nap511/viewmodel/FileViewModel.kt
index 50434ce..b245261 100644
--- a/app/src/main/java/github/zerorooot/nap511/viewmodel/FileViewModel.kt
+++ b/app/src/main/java/github/zerorooot/nap511/viewmodel/FileViewModel.kt
@@ -296,6 +296,7 @@ class FileViewModel(private val cookie: String, private val application: Applica
"jpg" -> fileBean.fileIco = R.drawable.png
"mp3" -> fileBean.fileIco = R.drawable.mp3
"txt" -> fileBean.fileIco = R.drawable.txt
+ "torrent" -> fileBean.fileIco = R.drawable.torrent
}
}
}
diff --git a/app/src/main/java/github/zerorooot/nap511/viewmodel/OfflineFileViewModel.kt b/app/src/main/java/github/zerorooot/nap511/viewmodel/OfflineFileViewModel.kt
index f453e2a..42ae69e 100644
--- a/app/src/main/java/github/zerorooot/nap511/viewmodel/OfflineFileViewModel.kt
+++ b/app/src/main/java/github/zerorooot/nap511/viewmodel/OfflineFileViewModel.kt
@@ -4,11 +4,15 @@ package github.zerorooot.nap511.viewmodel
import android.app.Activity
import android.app.Application
import android.widget.Toast
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import github.zerorooot.nap511.bean.OfflineInfo
import github.zerorooot.nap511.bean.OfflineTask
import github.zerorooot.nap511.bean.QuotaBean
+import github.zerorooot.nap511.bean.TorrentFileBean
import github.zerorooot.nap511.service.FileService
import github.zerorooot.nap511.service.OfflineService
import github.zerorooot.nap511.util.App
@@ -41,6 +45,12 @@ class OfflineFileViewModel(private val cookie: String, private val application:
lateinit var offlineTask: OfflineTask
+ private val _torrentBean = MutableStateFlow(TorrentFileBean())
+ var torrentBean = _torrentBean.asStateFlow()
+
+ //打开对话框相关
+ var isOpenCreateSelectTorrentFileDialog by mutableStateOf(false)
+
private val offlineService: OfflineService by lazy {
OfflineService.getInstance(cookie)
}
@@ -56,7 +66,7 @@ class OfflineFileViewModel(private val cookie: String, private val application:
viewModelScope.launch {
_isRefreshing.value = true
// val uid = sharedPreferencesUtil.get(ConfigUtil.uid)!!
- val uid = DataStoreUtil.getData(ConfigUtil.uid, "")
+ val uid = App.uid
val sign = offlineService.getSign().sign
_offlineInfo.value = offlineService.taskList(uid, sign)
setTaskInfo(_offlineInfo.value.tasks)
@@ -65,6 +75,42 @@ class OfflineFileViewModel(private val cookie: String, private val application:
}
}
+ fun getTorrentTask(sha1: String) {
+ viewModelScope.launch {
+ val sign = offlineService.getSign().sign
+ val torrentTask = offlineService.getTorrentTaskList(sha1, App.uid, sign)
+ torrentTask.fileSizeString = android.text.format.Formatter.formatFileSize(
+ application, torrentTask.fileSize
+ ) + " "
+// torrentTask.torrentFileListWeb.removeIf { i -> i.wanted == -1 }
+ torrentTask.torrentFileListWeb.forEach { b ->
+ b.sizeString = android.text.format.Formatter.formatFileSize(
+ application, b.size
+ ) + " "
+ }
+ _torrentBean.value = torrentTask
+ }
+ }
+
+ fun addTorrentTask(torrentFileBean: TorrentFileBean, wanted: String) {
+ viewModelScope.launch {
+ val sign = offlineService.getSign().sign
+ val addTorrentTask = offlineService.addTorrentTask(
+ torrentFileBean.infoHash,
+ wanted,
+ torrentFileBean.torrentName,
+ App.uid,
+ sign
+ )
+ val message = if (addTorrentTask.state) {
+ "任务添加成功,文件已保存至 /云下载/${torrentFileBean.torrentName}"
+ } else {
+ "任务添加失败,${addTorrentTask.errorMsg}"
+ }
+ App.instance.toast(message)
+ }
+ }
+
private fun setTaskInfo(tasks: ArrayList) {
tasks.forEach { offlineTask ->
offlineTask.timeString =
@@ -138,7 +184,7 @@ class OfflineFileViewModel(private val cookie: String, private val application:
val map = HashMap()
map["savepath"] = ""
map["wp_path_id"] = currentCid
- map["uid"] = DataStoreUtil.getData(ConfigUtil.uid, "")
+ map["uid"] = App.uid
map["sign"] = offlineService.getSign().sign
map["time"] = (System.currentTimeMillis() / 1000).toString()
list.forEachIndexed { index, s ->
diff --git a/app/src/main/res/drawable/torrent.xml b/app/src/main/res/drawable/torrent.xml
new file mode 100644
index 0000000..28f0ef1
--- /dev/null
+++ b/app/src/main/res/drawable/torrent.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file