Jelajahi Sumber

增加导航,可以加密,但是与python不兼容

sequoia00 1 bulan lalu
induk
melakukan
baf09c57bb

TEMPAT SAMPAH
09-29.jpg


TEMPAT SAMPAH
09-29_restored.jpg


+ 56 - 0
README.md

@@ -68,6 +68,62 @@ python fast_media_lock.py unlock "D:\\videos" --password "你的密码"
 python fast_media_lock.py status "D:\\videos"
 ```
 
+
+## Termux(Android 手机)
+
+Termux 是 Android 上的终端模拟器,可直接运行此脚本。
+
+### 安装 Python
+
+```bash
+pkg update && pkg install python -y
+```
+
+### 部署脚本
+
+将项目文件传输到 Termux(通过 USB、SSH 或云盘),然后在 Termux 中执行:
+
+```bash
+# 进入存放目录
+cd ~/lock_folder
+
+# 赋予执行权限
+chmod +x fast_media_lock.py fast-media-lock
+
+# 可选:安装全局命令(存入 ~/.local/bin)
+mkdir -p ~/.local/bin
+cp fast-media-lock ~/.local/bin/
+chmod +x ~/.local/bin/fast-media-lock
+```
+
+> 注:Termux 默认 `~/.local/bin` 已在 PATH 中,安装后可直接在任意目录使用 `fast-media-lock` 命令。
+
+### 加密目录
+
+```bash
+python3 fast_media_lock.py lock /sdcard/DCIM --password "你的密码"
+```
+
+### 解密文件
+
+```bash
+python3 fast_media_lock.py unlock /sdcard/DCIM/xxx.jpg.lockx --password "你的密码"
+```
+
+### 权限说明
+
+Termux 默认只能访问自家存储。要读写外部存储(如 DCIM),需先授权:
+
+```bash
+termux-setup-storage
+```
+
+授权后外部文件可通过 `~/storage/shared/` 路径访问,例如:
+
+```bash
+python3 fast_media_lock.py lock ~/storage/shared/DCIM --password "你的密码"
+```
+
 ## Ubuntu 全局命令(非定时)
 
 项目里已提供:

+ 3 - 3
androidApp/app/src/main/java/com/example/lockx/crypto/CryptoUtils.kt

@@ -208,7 +208,7 @@ object CryptoUtils {
         while (i < data.size) {
             val blockSize = if (32 < data.size - i) 32 else data.size - i
             val counter = blockOffset + i / 32
-            val counterBytes = counter.toLong().toLEBytes(8)
+            val counterBytes = counter.toLong().toBEBytes(8)
             val combined = ByteArray(nonce.size + counterBytes.size)
             nonce.copyInto(combined, 0)
             counterBytes.copyInto(combined, nonce.size)
@@ -219,9 +219,9 @@ object CryptoUtils {
         return out
     }
 
-    private fun Long.toLEBytes(size: Int): ByteArray {
+    private fun Long.toBEBytes(size: Int): ByteArray {
         val b = ByteArray(size)
-        for (i in 0 until size) b[i] = ((this shr (i * 8)) and 0xFFL).toByte()
+        for (i in size - 1 downTo 0) b[i] = ((this shr (i * 8)) and 0xFFL).toByte()
         return b
     }
 

+ 48 - 8
androidApp/app/src/main/java/com/example/lockx/ui/FileBrowserScreen.kt

@@ -1,6 +1,9 @@
 package com.example.lockx.ui
 
 import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectHorizontalDragGestures
+import androidx.compose.ui.input.pointer.pointerInput
+import java.io.File
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
@@ -195,21 +198,46 @@ fun FileBrowserScreen(viewModel: FileBrowserViewModel, context: android.content.
                     modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
                     verticalAlignment = Alignment.CenterVertically
                 ) {
+                    // Up button (always show when able to go up)
                     if (mode == BrowserMode.Local && viewModel.localUpEnabled) {
                         IconButton(onClick = { viewModel.goUpLocal() }) {
-                            Icon(Icons.Default.ArrowBack, contentDescription = "返回上级")
+                            Icon(Icons.Default.ArrowBack, contentDescription = "上一级")
                         }
                     }
-                    if (mode == BrowserMode.Sftp && viewModel.sftpConnected) {
+                    if (mode == BrowserMode.Sftp && viewModel.sftpConnected && File(viewModel.sftpCurrentPath).parent != null) {
                         IconButton(onClick = { viewModel.goUpSftp() }) {
-                            Icon(Icons.Default.ArrowBack, contentDescription = "返回上级")
+                            Icon(Icons.Default.ArrowBack, contentDescription = "上一级")
+                        }
+                    }
+                    // Back button (navigation history)
+                    if (mode == BrowserMode.Local && viewModel.localBackHistory.isNotEmpty()) {
+                        IconButton(onClick = { viewModel.goBackLocal() }) {
+                            Icon(Icons.Default.ArrowBack, contentDescription = "返回")
+                        }
+                    }
+                    if (mode == BrowserMode.Sftp && viewModel.sftpBackHistory.isNotEmpty()) {
+                        IconButton(onClick = { viewModel.goBackSftp() }) {
+                            Icon(Icons.Default.ArrowBack, contentDescription = "返回")
+                        }
+                    }
+                    // Forward button (navigation history)
+                    if (mode == BrowserMode.Local && viewModel.localForwardHistory.isNotEmpty()) {
+                        IconButton(onClick = { viewModel.goForwardLocal() }) {
+                            Icon(Icons.Default.ArrowForward, contentDescription = "前进")
+                        }
+                    }
+                    if (mode == BrowserMode.Sftp && viewModel.sftpForwardHistory.isNotEmpty()) {
+                        IconButton(onClick = { viewModel.goForwardSftp() }) {
+                            Icon(Icons.Default.ArrowForward, contentDescription = "前进")
                         }
                     }
+                    // Path label (clickable)
+                    val currentPath = when (mode) {
+                        BrowserMode.Local -> viewModel.localCurrentPath
+                        BrowserMode.Sftp -> if (viewModel.sftpConnected) viewModel.sftpCurrentPath else ""
+                    }
                     Text(
-                        when (mode) {
-                            BrowserMode.Local -> viewModel.localCurrentPath
-                            BrowserMode.Sftp -> if (viewModel.sftpConnected) viewModel.sftpCurrentPath else ""
-                        },
+                        currentPath,
                         style = MaterialTheme.typography.bodyMedium,
                         fontWeight = FontWeight.Medium,
                         modifier = Modifier.weight(1f),
@@ -300,7 +328,19 @@ private fun LocalFileList(viewModel: FileBrowserViewModel, onPlayClick: (FileIte
         }
         return
     }
-    LazyColumn(modifier = Modifier.fillMaxSize()) {
+    LazyColumn(
+        modifier = Modifier
+            .fillMaxSize()
+            .pointerInput(Unit) {
+                detectHorizontalDragGestures(
+                    onHorizontalDrag = { change, dragAmount ->
+                        if (dragAmount < -200f && viewModel.localUpEnabled) {
+                            viewModel.goUpLocal()
+                        }
+                    }
+                )
+            }
+    ) {
         items(viewModel.localFiles, key = { it.path }) { item ->
             val isSelected = viewModel.selectedFiles.contains(item.path) ||
                 (viewModel.selectedFile is FileOpTarget.Local && (viewModel.selectedFile as FileOpTarget.Local).item.path == item.path)

+ 73 - 4
androidApp/app/src/main/java/com/example/lockx/ui/FileBrowserViewModel.kt

@@ -21,6 +21,7 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.io.File
+import java.util.ArrayList
 import android.os.Environment
 import android.util.Log
 import android.widget.Toast
@@ -41,6 +42,11 @@ class FileBrowserViewModel(private val context: Context) : ViewModel() {
         private set
     var localUpEnabled by mutableStateOf(false)
         private set
+    var localBackHistory by mutableStateOf<List<String>>(emptyList())
+    var localForwardHistory by mutableStateOf<List<String>>(emptyList())
+
+    var sftpBackHistory by mutableStateOf<List<String>>(emptyList())
+    var sftpForwardHistory by mutableStateOf<List<String>>(emptyList())
 
     // --- SFTP ---
     val sftpManager = SftpManager()
@@ -169,18 +175,45 @@ class FileBrowserViewModel(private val context: Context) : ViewModel() {
     // ======================== LOCAL ========================
     fun refreshLocal(path: String? = null) {
         val dir = File(path ?: localCurrentPath)
-        localCurrentPath = dir.absolutePath
+        val newPath = dir.absolutePath
+        if (newPath != localCurrentPath) {
+            localBackHistory = localBackHistory + localCurrentPath
+            localForwardHistory = emptyList()
+        }
+        localCurrentPath = newPath
         localFiles = FileUtils.listLocalFiles(dir)
         localUpEnabled = dir.parentFile != null
     }
 
+    fun goBackLocal() {
+        if (localBackHistory.isEmpty()) return
+        val prev = localBackHistory.last()
+        localBackHistory = localBackHistory.dropLast(1)
+        localForwardHistory = listOf(localCurrentPath) + localForwardHistory
+        refreshLocal(prev)
+    }
+
+    fun goForwardLocal() {
+        if (localForwardHistory.isEmpty()) return
+        val next = localForwardHistory.first()
+        localForwardHistory = localForwardHistory.drop(1)
+        localBackHistory = localBackHistory + localCurrentPath
+        refreshLocal(next)
+    }
+
     fun navigateLocal(item: FileItem) {
         if (isSelectionMode) { toggleSelection(item.path); return }
-        if (item.isDirectory) { refreshLocal(item.path) }
-        else { selectedFile = FileOpTarget.Local(item); selectedFiles = setOf(item.path) }
+        if (item.isDirectory) {
+            refreshLocal(item.path)
+        } else {
+            selectedFile = FileOpTarget.Local(item)
+            selectedFiles = setOf(item.path)
+        }
     }
 
-    fun goUpLocal() { File(localCurrentPath).parentFile?.let { refreshLocal(it.absolutePath) } }
+    fun goUpLocal() {
+        File(localCurrentPath).parentFile?.let { refreshLocal(it.absolutePath) }
+    }
 
     // ======================== SFTP ========================
     fun connectSsh(cfg: SshConfig) {
@@ -206,6 +239,10 @@ class FileBrowserViewModel(private val context: Context) : ViewModel() {
     fun refreshSftp(path: String? = null) {
         viewModelScope.launch(Dispatchers.IO) {
             val p = path ?: sftpCurrentPath
+            if (p != sftpCurrentPath) {
+                sftpBackHistory = sftpBackHistory + sftpCurrentPath
+                sftpForwardHistory = emptyList()
+            }
             val result = sftpManager.listDirectory(p)
             if (result.isSuccess) {
                 sftpCurrentPath = p
@@ -216,6 +253,26 @@ class FileBrowserViewModel(private val context: Context) : ViewModel() {
         }
     }
 
+    fun goBackSftp() {
+        if (sftpBackHistory.isEmpty()) return
+        viewModelScope.launch {
+            val prev = sftpBackHistory.last()
+            sftpBackHistory = sftpBackHistory.dropLast(1)
+            sftpForwardHistory = listOf(sftpCurrentPath) + sftpForwardHistory
+            refreshSftp(prev)
+        }
+    }
+
+    fun goForwardSftp() {
+        if (sftpForwardHistory.isEmpty()) return
+        viewModelScope.launch {
+            val next = sftpForwardHistory.first()
+            sftpForwardHistory = sftpForwardHistory.drop(1)
+            sftpBackHistory = sftpBackHistory + sftpCurrentPath
+            refreshSftp(next)
+        }
+    }
+
     fun navigateSftp(item: SftpFile) {
         if (item.isDirectory) { refreshSftp(item.path) }
         else { selectedFile = FileOpTarget.Sftp(item); selectedFiles = setOf(item.path) }
@@ -281,6 +338,17 @@ class FileBrowserViewModel(private val context: Context) : ViewModel() {
                 operationState = UiState.Error("操作异常: ${e.message}")
             }
             currentOperation = null
+            clearOperationState()
+        }
+    }
+
+    private fun clearOperationState() {
+        viewModelScope.launch {
+            delay(1000)
+            if (operationState !is UiState.Idle) {
+                operationState = UiState.Idle
+                selectedFile = null
+            }
         }
     }
 
@@ -329,6 +397,7 @@ class FileBrowserViewModel(private val context: Context) : ViewModel() {
                 clearSelection()
             }
             currentOperation = null
+            clearOperationState()
         }
     }
 

TEMPAT SAMPAH
androidx/compose/foundation/gestures/DragGestureDetectorKt.class