feat: UI compact layout + unlock support + ResticBinary init at startup
This commit is contained in:
@@ -13,6 +13,8 @@ import androidx.viewpager2.widget.ViewPager2
|
|||||||
import com.example.androidbackupgui.databinding.ActivityMainBinding
|
import com.example.androidbackupgui.databinding.ActivityMainBinding
|
||||||
import com.example.androidbackupgui.root.RootShell
|
import com.example.androidbackupgui.root.RootShell
|
||||||
import com.example.androidbackupgui.backup.LogUtil
|
import com.example.androidbackupgui.backup.LogUtil
|
||||||
|
import com.example.androidbackupgui.backup.ResticBinary
|
||||||
|
import com.example.androidbackupgui.backup.ResticWrapper
|
||||||
import com.example.androidbackupgui.ui.BackupFragment
|
import com.example.androidbackupgui.ui.BackupFragment
|
||||||
import com.example.androidbackupgui.ui.ConfigFragment
|
import com.example.androidbackupgui.ui.ConfigFragment
|
||||||
import com.example.androidbackupgui.ui.RestoreFragment
|
import com.example.androidbackupgui.ui.RestoreFragment
|
||||||
@@ -39,6 +41,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// Configure libsu with global mount namespace support
|
// Configure libsu with global mount namespace support
|
||||||
RootShell.configure()
|
RootShell.configure()
|
||||||
|
|
||||||
|
// Initialize restic binary path
|
||||||
|
ResticBinary.prepare(this)?.let { ResticWrapper.binaryPath = it }
|
||||||
|
|
||||||
// Request root access on startup
|
// Request root access on startup
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -61,6 +61,36 @@ class ResticMaintenance(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Unlock ──────────────────────────────────────────
|
||||||
|
|
||||||
|
suspend fun unlock(
|
||||||
|
repoPath: String,
|
||||||
|
password: String,
|
||||||
|
backend: String = "local",
|
||||||
|
backendUrl: String = "",
|
||||||
|
backendUser: String = "",
|
||||||
|
backendPass: String = "",
|
||||||
|
backendShare: String = "",
|
||||||
|
): AppResult<String> =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (backend == "local") {
|
||||||
|
val env = envResolver.buildLocalEnv(repoPath, password, cacheDir)
|
||||||
|
val result = runner.runRestic(env, "unlock")
|
||||||
|
if (result.exitCode == 0) AppResult.Success(result.stdout)
|
||||||
|
else err(AppError.Restic("restic unlock 失败", result.exitCode, result.stderr))
|
||||||
|
} else {
|
||||||
|
bridgeRunner.withBridge(
|
||||||
|
backend, backendUrl, backendUser, backendPass, backendShare,
|
||||||
|
backendDomain, repoPath, File(cacheDir)
|
||||||
|
) { bridgeUrl, authToken ->
|
||||||
|
val env = envResolver.buildBridgeEnv(password, bridgeUrl, cacheDir, authToken)
|
||||||
|
val result = runner.runRestic(env, "unlock")
|
||||||
|
if (result.exitCode == 0) AppResult.Success(result.stdout)
|
||||||
|
else err(AppError.Restic("restic unlock 失败", result.exitCode, result.stderr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Check ──────────────────────────────────────────
|
// ── Check ──────────────────────────────────────────
|
||||||
|
|
||||||
suspend fun check(
|
suspend fun check(
|
||||||
|
|||||||
@@ -359,6 +359,20 @@ object ResticWrapper {
|
|||||||
backend, backendUrl, backendUser, backendPass, backendShare
|
backend, backendUrl, backendUser, backendPass, backendShare
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun unlock(
|
||||||
|
repoPath: String,
|
||||||
|
password: String,
|
||||||
|
backend: String = "local",
|
||||||
|
backendUrl: String = "",
|
||||||
|
backendUser: String = "",
|
||||||
|
backendPass: String = "",
|
||||||
|
backendShare: String = "",
|
||||||
|
): AppResult<String> =
|
||||||
|
maintenance.unlock(
|
||||||
|
repoPath, password,
|
||||||
|
backend, backendUrl, backendUser, backendPass, backendShare,
|
||||||
|
)
|
||||||
|
|
||||||
// ── Public URL helper ──────────────────────────────
|
// ── Public URL helper ──────────────────────────────
|
||||||
|
|
||||||
/** Build a display-friendly repository URL for UI. */
|
/** Build a display-friendly repository URL for UI. */
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ class BackupFragment : Fragment() {
|
|||||||
val names = userList.map { (id, name) -> "$name (ID: $id)" }
|
val names = userList.map { (id, name) -> "$name (ID: $id)" }
|
||||||
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, names)
|
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, names)
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
binding.userSelector.adapter = adapter
|
binding.backupUserSelector.adapter = adapter
|
||||||
binding.userSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.backupUserSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
selectedUserId = userList.getOrNull(position)?.first ?: 0
|
selectedUserId = userList.getOrNull(position)?.first ?: 0
|
||||||
}
|
}
|
||||||
@@ -401,7 +401,7 @@ class BackupFragment : Fragment() {
|
|||||||
|
|
||||||
private fun updateOutputPathDisplay() {
|
private fun updateOutputPathDisplay() {
|
||||||
val path = config.outputPath.ifEmpty { requireContext().filesDir.absolutePath }
|
val path = config.outputPath.ifEmpty { requireContext().filesDir.absolutePath }
|
||||||
binding.outputPathLabel.text = path
|
binding.outputPathLabel.text = "目录: $path"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ class ConfigFragment : Fragment() {
|
|||||||
binding.initResticButton.setOnClickListener { initResticRepo() }
|
binding.initResticButton.setOnClickListener { initResticRepo() }
|
||||||
binding.resticStatsButton.setOnClickListener { showResticStats() }
|
binding.resticStatsButton.setOnClickListener { showResticStats() }
|
||||||
binding.resticPruneButton.setOnClickListener { pruneResticSnapshots() }
|
binding.resticPruneButton.setOnClickListener { pruneResticSnapshots() }
|
||||||
|
binding.resticUnlockButton.setOnClickListener {
|
||||||
|
vm.unlockResticRepo(readResticForm())
|
||||||
|
}
|
||||||
|
|
||||||
// Initial async status check
|
// Initial async status check
|
||||||
refreshResticStatus()
|
refreshResticStatus()
|
||||||
@@ -140,6 +143,8 @@ class ConfigFragment : Fragment() {
|
|||||||
binding.resticStatsButton.visibility = if (statsButtonVisible) View.VISIBLE else View.GONE
|
binding.resticStatsButton.visibility = if (statsButtonVisible) View.VISIBLE else View.GONE
|
||||||
binding.resticPruneButton.isEnabled = pruneButtonEnabled
|
binding.resticPruneButton.isEnabled = pruneButtonEnabled
|
||||||
binding.resticPruneButton.visibility = if (pruneButtonVisible) View.VISIBLE else View.GONE
|
binding.resticPruneButton.visibility = if (pruneButtonVisible) View.VISIBLE else View.GONE
|
||||||
|
binding.resticUnlockButton.isEnabled = unlockButtonEnabled
|
||||||
|
binding.resticUnlockButton.visibility = if (unlockButtonVisible) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ data class ResticStatus(
|
|||||||
val statsButtonVisible: Boolean = false,
|
val statsButtonVisible: Boolean = false,
|
||||||
val statsButtonEnabled: Boolean = true,
|
val statsButtonEnabled: Boolean = true,
|
||||||
val pruneButtonVisible: Boolean = false,
|
val pruneButtonVisible: Boolean = false,
|
||||||
val pruneButtonEnabled: Boolean = true
|
val pruneButtonEnabled: Boolean = true,
|
||||||
|
val unlockButtonVisible: Boolean = false,
|
||||||
|
val unlockButtonEnabled: Boolean = true
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Restic credential/form snapshot passed from Fragment on every user interaction. */
|
/** Restic credential/form snapshot passed from Fragment on every user interaction. */
|
||||||
@@ -245,16 +247,39 @@ class ConfigViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
_uiState.update { it.copy(resticStatus = ResticStatus(
|
_uiState.update { it.copy(resticStatus = ResticStatus(
|
||||||
message = "仓库就绪,${snapshots.size} 个快照",
|
message = "仓库就绪,${snapshots.size} 个快照",
|
||||||
snapshotCount = snapshots.size,
|
snapshotCount = snapshots.size,
|
||||||
initButtonVisible = false, statsButtonVisible = true, pruneButtonVisible = true
|
initButtonVisible = false, statsButtonVisible = true, pruneButtonVisible = true,
|
||||||
|
unlockButtonVisible = true
|
||||||
))}
|
))}
|
||||||
} else {
|
} else {
|
||||||
|
val errMsg = snapshotsResult.errorOrNull()?.message ?: ""
|
||||||
|
val hasLock = errMsg.contains("lock", ignoreCase = true) || errMsg.contains("already locked", ignoreCase = true)
|
||||||
_uiState.update { it.copy(resticStatus = ResticStatus(
|
_uiState.update { it.copy(resticStatus = ResticStatus(
|
||||||
message = "仓库未初始化或认证失败",
|
message = if (hasLock) "仓库被锁定,请先解锁" else "仓库未初始化或认证失败",
|
||||||
initButtonVisible = true, statsButtonVisible = false, pruneButtonVisible = false
|
initButtonVisible = !hasLock, statsButtonVisible = false, pruneButtonVisible = false,
|
||||||
|
unlockButtonVisible = hasLock
|
||||||
))}
|
))}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun unlockResticRepo(form: ResticForm) {
|
||||||
|
_uiState.update { it.copy(resticStatus = it.resticStatus.copy(
|
||||||
|
message = "正在解锁仓库…", unlockButtonEnabled = false
|
||||||
|
))}
|
||||||
|
viewModelScope.launch {
|
||||||
|
ResticWrapper.backendDomain = form.backendDomain
|
||||||
|
val result = ResticWrapper.unlock(form.repo, form.password,
|
||||||
|
backend = form.backend, backendUrl = form.backendUrl,
|
||||||
|
backendUser = form.backendUser, backendPass = form.backendPass,
|
||||||
|
backendShare = form.backendShare,
|
||||||
|
)
|
||||||
|
_uiState.update { it.copy(resticStatus = it.resticStatus.copy(
|
||||||
|
message = if (result.isSuccess) "解锁完成" else "解锁失败: ${result.errorOrNull()?.message}",
|
||||||
|
unlockButtonEnabled = true
|
||||||
|
))}
|
||||||
|
refreshResticStatus(form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun showResticStats(form: ResticForm) {
|
fun showResticStats(form: ResticForm) {
|
||||||
_uiState.update { it.copy(resticStatus = it.resticStatus.copy(
|
_uiState.update { it.copy(resticStatus = it.resticStatus.copy(
|
||||||
message = "正在读取统计…", statsButtonEnabled = false
|
message = "正在读取统计…", statsButtonEnabled = false
|
||||||
@@ -303,6 +328,15 @@ class ConfigViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
_operationEvents.emit(OperationEvent.PruneStarted)
|
_operationEvents.emit(OperationEvent.PruneStarted)
|
||||||
|
|
||||||
|
// Remove stale locks before forget/prune
|
||||||
|
ResticWrapper.backendDomain = form.backendDomain
|
||||||
|
ResticWrapper.unlock(form.repo, form.password,
|
||||||
|
backend = form.backend, backendUrl = form.backendUrl,
|
||||||
|
backendUser = form.backendUser, backendPass = form.backendPass,
|
||||||
|
backendShare = form.backendShare,
|
||||||
|
)
|
||||||
|
|
||||||
val forgetResult = ResticWrapper.forget(form.repo, form.password,
|
val forgetResult = ResticWrapper.forget(form.repo, form.password,
|
||||||
keepDaily = 7, keepWeekly = 4, keepMonthly = 3,
|
keepDaily = 7, keepWeekly = 4, keepMonthly = 3,
|
||||||
backend = form.backend, backendUrl = form.backendUrl,
|
backend = form.backend, backendUrl = form.backendUrl,
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ class RestoreFragment : Fragment() {
|
|||||||
val names = userList.map { (id, name) -> "$name (ID: $id)" }
|
val names = userList.map { (id, name) -> "$name (ID: $id)" }
|
||||||
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, names)
|
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, names)
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
binding.userSelector.adapter = adapter
|
binding.restoreUserSelector.adapter = adapter
|
||||||
binding.userSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.restoreUserSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
selectedUserId = userList.getOrNull(position)?.first ?: 0
|
selectedUserId = userList.getOrNull(position)?.first ?: 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
android:padding="@dimen/fragment_horizontal_padding"
|
android:padding="@dimen/fragment_horizontal_padding"
|
||||||
android:background="?attr/colorSurface">
|
android:background="?attr/colorSurface">
|
||||||
|
|
||||||
|
<!-- Title + user selector -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -18,38 +19,33 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="应用备份"
|
android:text="应用备份"
|
||||||
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
|
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
|
||||||
android:textColor="?attr/colorOnSurface" />
|
android:textColor="?attr/colorOnSurface" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/scanButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="扫描应用"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="用户: "
|
android:text="用户: "
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/userSelector"
|
android:id="@+id/backupUserSelector"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1" />
|
android:minWidth="80dp"
|
||||||
|
android:spinnerMode="dropdown" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/scanButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="扫描"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Output path + sort toolbar -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -57,13 +53,6 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="输出目录: "
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/outputPathLabel"
|
android:id="@+id/outputPathLabel"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -71,7 +60,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:ellipsize="middle"
|
android:ellipsize="middle"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
@@ -80,9 +69,11 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:text="修改" />
|
android:text="修改"
|
||||||
|
android:textSize="12sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Sort/filter row -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -133,10 +124,11 @@
|
|||||||
style="@style/Widget.Material3.Button.TonalButton" />
|
style="@style/Widget.Material3.Button.TonalButton" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- System apps toggle + status -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="2dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
@@ -144,46 +136,49 @@
|
|||||||
android:id="@+id/showSystemSwitch"
|
android:id="@+id/showSystemSwitch"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="显示系统应用"
|
android:text="系统应用"
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
|
||||||
android:checked="false" />
|
android:checked="false" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/statusText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:text="点击扫描"
|
||||||
|
android:textAppearance="@style/TextAppearance.Material3.LabelSmall"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:indicatorColor="?attr/colorPrimary"
|
app:indicatorColor="?attr/colorPrimary"
|
||||||
app:trackColor="?attr/colorSurfaceVariant" />
|
app:trackColor="?attr/colorSurfaceVariant" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/statusText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:text="点击扫描以载入应用列表"
|
|
||||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/appList"
|
android:id="@+id/appList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="4dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingBottom="8dp" />
|
android:paddingBottom="4dp" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/backupButton"
|
android:id="@+id/backupButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="8dp"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:text="开始备份选中应用"
|
android:text="开始备份选中应用"
|
||||||
style="@style/Widget.Material3.Button" />
|
style="@style/Widget.Material3.Button" />
|
||||||
|
|||||||
@@ -173,7 +173,8 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:singleSelection="true"
|
app:singleSelection="true"
|
||||||
app:selectionRequired="true">
|
app:selectionRequired="true"
|
||||||
|
app:checkedButton="@id/resticBackendLocal">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/resticBackendLocal"
|
android:id="@+id/resticBackendLocal"
|
||||||
@@ -267,6 +268,7 @@
|
|||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:hint="密码"
|
android:hint="密码"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:endIconMode="password_toggle"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/resticBackendPassEdit"
|
android:id="@+id/resticBackendPassEdit"
|
||||||
@@ -308,10 +310,12 @@
|
|||||||
|
|
||||||
<!-- Repo encryption password (always shown) -->
|
<!-- Repo encryption password (always shown) -->
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/resticPasswordLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:hint="仓库加密密码 (请妥善保管,遗失即无法恢复)"
|
android:hint="仓库加密密码 (请妥善保管,遗失即无法恢复)"
|
||||||
|
app:endIconMode="password_toggle"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/resticPasswordEdit"
|
android:id="@+id/resticPasswordEdit"
|
||||||
@@ -360,6 +364,14 @@
|
|||||||
android:text="清理"
|
android:text="清理"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
style="@style/Widget.Material3.Button.TextButton" />
|
style="@style/Widget.Material3.Button.TextButton" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/resticUnlockButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="解锁"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/userSelector"
|
android:id="@+id/restoreUserSelector"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|||||||
77
gradlew.bat
vendored
Normal file
77
gradlew.bat
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %OS%==Windows_NT setlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
Reference in New Issue
Block a user