mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
This commit is contained in:
parent
36621c31b3
commit
8c12ee172f
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
<!-- save image to album -->
|
<!-- save image to album, save file to Downloads -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="28" />
|
android:maxSdkVersion="28" />
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package li.songe.gkd.data
|
package li.songe.gkd.data
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.blankj.utilcode.util.LogUtils
|
import com.blankj.utilcode.util.LogUtils
|
||||||
import com.blankj.utilcode.util.UriUtils
|
import com.blankj.utilcode.util.UriUtils
|
||||||
|
@ -17,11 +16,11 @@ import li.songe.gkd.util.exportZipDir
|
||||||
import li.songe.gkd.util.importZipDir
|
import li.songe.gkd.util.importZipDir
|
||||||
import li.songe.gkd.util.json
|
import li.songe.gkd.util.json
|
||||||
import li.songe.gkd.util.resetDirectory
|
import li.songe.gkd.util.resetDirectory
|
||||||
import li.songe.gkd.util.shareFile
|
|
||||||
import li.songe.gkd.util.subsIdToRawFlow
|
import li.songe.gkd.util.subsIdToRawFlow
|
||||||
import li.songe.gkd.util.subsItemsFlow
|
import li.songe.gkd.util.subsItemsFlow
|
||||||
import li.songe.gkd.util.toast
|
import li.songe.gkd.util.toast
|
||||||
import li.songe.gkd.util.updateSubscription
|
import li.songe.gkd.util.updateSubscription
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
private data class TransferData(
|
private data class TransferData(
|
||||||
|
@ -52,8 +51,7 @@ private suspend fun importTransferData(transferData: TransferData): Boolean {
|
||||||
return hasNewSubsItem
|
return hasNewSubsItem
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun exportData(context: Context, subsIds: Collection<Long>) {
|
suspend fun exportData(subsIds: Collection<Long>):File {
|
||||||
if (subsIds.isEmpty()) return
|
|
||||||
exportZipDir.resetDirectory()
|
exportZipDir.resetDirectory()
|
||||||
val dataFile = exportZipDir.resolve("${TransferData.TYPE}.json")
|
val dataFile = exportZipDir.resolve("${TransferData.TYPE}.json")
|
||||||
dataFile.writeText(
|
dataFile.writeText(
|
||||||
|
@ -74,7 +72,7 @@ suspend fun exportData(context: Context, subsIds: Collection<Long>) {
|
||||||
ZipUtils.zipFiles(listOf(dataFile, files), file)
|
ZipUtils.zipFiles(listOf(dataFile, files), file)
|
||||||
dataFile.delete()
|
dataFile.delete()
|
||||||
files.deleteRecursively()
|
files.deleteRecursively()
|
||||||
context.shareFile(file, "分享数据文件")
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun importData(uri: Uri) {
|
suspend fun importData(uri: Uri) {
|
||||||
|
|
|
@ -125,7 +125,7 @@ val canDrawOverlaysState by lazy {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val canSaveToAlbumState by lazy {
|
val canWriteExternalStorage by lazy {
|
||||||
PermissionState(
|
PermissionState(
|
||||||
check = {
|
check = {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
@ -170,7 +170,7 @@ suspend fun updatePermissionState() {
|
||||||
arrayOf(
|
arrayOf(
|
||||||
notificationState,
|
notificationState,
|
||||||
canDrawOverlaysState,
|
canDrawOverlaysState,
|
||||||
canSaveToAlbumState,
|
canWriteExternalStorage,
|
||||||
shizukuOkState
|
shizukuOkState
|
||||||
).forEach { it.updateAndGet() }
|
).forEach { it.updateAndGet() }
|
||||||
if (canQueryPkgState.stateFlow.value != canQueryPkgState.updateAndGet()) {
|
if (canQueryPkgState.stateFlow.value != canQueryPkgState.updateAndGet()) {
|
||||||
|
|
|
@ -55,7 +55,7 @@ import li.songe.gkd.MainActivity
|
||||||
import li.songe.gkd.data.Snapshot
|
import li.songe.gkd.data.Snapshot
|
||||||
import li.songe.gkd.db.DbSet
|
import li.songe.gkd.db.DbSet
|
||||||
import li.songe.gkd.debug.SnapshotExt
|
import li.songe.gkd.debug.SnapshotExt
|
||||||
import li.songe.gkd.permission.canSaveToAlbumState
|
import li.songe.gkd.permission.canWriteExternalStorage
|
||||||
import li.songe.gkd.permission.requiredPermission
|
import li.songe.gkd.permission.requiredPermission
|
||||||
import li.songe.gkd.ui.component.StartEllipsisText
|
import li.songe.gkd.ui.component.StartEllipsisText
|
||||||
import li.songe.gkd.ui.destinations.ImagePreviewPageDestination
|
import li.songe.gkd.ui.destinations.ImagePreviewPageDestination
|
||||||
|
@ -66,6 +66,7 @@ import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.LocalPickContentLauncher
|
import li.songe.gkd.util.LocalPickContentLauncher
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
|
import li.songe.gkd.util.saveFileToDownloads
|
||||||
import li.songe.gkd.util.shareFile
|
import li.songe.gkd.util.shareFile
|
||||||
import li.songe.gkd.util.snapshotZipDir
|
import li.songe.gkd.util.snapshotZipDir
|
||||||
import li.songe.gkd.util.throttle
|
import li.songe.gkd.util.throttle
|
||||||
|
@ -195,17 +196,31 @@ fun SnapshotPage() {
|
||||||
)
|
)
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
Text(
|
Text(
|
||||||
text = "分享",
|
text = "分享数据",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(onClick = vm.viewModelScope.launchAsFn {
|
.clickable(onClick = vm.viewModelScope.launchAsFn {
|
||||||
val zipFile =
|
|
||||||
SnapshotExt.getSnapshotZipFile(
|
|
||||||
snapshotVal.id,
|
|
||||||
snapshotVal.appId,
|
|
||||||
snapshotVal.activityId
|
|
||||||
)
|
|
||||||
context.shareFile(zipFile, "分享快照文件")
|
|
||||||
selectedSnapshot = null
|
selectedSnapshot = null
|
||||||
|
val zipFile = SnapshotExt.getSnapshotZipFile(
|
||||||
|
snapshotVal.id,
|
||||||
|
snapshotVal.appId,
|
||||||
|
snapshotVal.activityId
|
||||||
|
)
|
||||||
|
context.shareFile(zipFile, "分享快照文件")
|
||||||
|
})
|
||||||
|
.then(modifier)
|
||||||
|
)
|
||||||
|
HorizontalDivider()
|
||||||
|
Text(
|
||||||
|
text = "保存到下载",
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = vm.viewModelScope.launchAsFn {
|
||||||
|
selectedSnapshot = null
|
||||||
|
val zipFile = SnapshotExt.getSnapshotZipFile(
|
||||||
|
snapshotVal.id,
|
||||||
|
snapshotVal.appId,
|
||||||
|
snapshotVal.activityId
|
||||||
|
)
|
||||||
|
context.saveFileToDownloads(zipFile)
|
||||||
})
|
})
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
)
|
)
|
||||||
|
@ -236,7 +251,7 @@ fun SnapshotPage() {
|
||||||
text = "保存截图到相册",
|
text = "保存截图到相册",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(onClick = vm.viewModelScope.launchAsFn {
|
.clickable(onClick = vm.viewModelScope.launchAsFn {
|
||||||
requiredPermission(context, canSaveToAlbumState)
|
requiredPermission(context, canWriteExternalStorage)
|
||||||
ImageUtils.save2Album(
|
ImageUtils.save2Album(
|
||||||
ImageUtils.getBitmap(snapshotVal.screenshotFile),
|
ImageUtils.getBitmap(snapshotVal.screenshotFile),
|
||||||
Bitmap.CompressFormat.PNG,
|
Bitmap.CompressFormat.PNG,
|
||||||
|
|
|
@ -28,7 +28,6 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.blankj.utilcode.util.ClipboardUtils
|
import com.blankj.utilcode.util.ClipboardUtils
|
||||||
import com.ramcosta.composedestinations.navigation.navigate
|
import com.ramcosta.composedestinations.navigation.navigate
|
||||||
|
@ -36,10 +35,10 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import li.songe.gkd.data.RawSubscription
|
import li.songe.gkd.data.RawSubscription
|
||||||
import li.songe.gkd.data.SubsItem
|
import li.songe.gkd.data.SubsItem
|
||||||
import li.songe.gkd.data.deleteSubscription
|
import li.songe.gkd.data.deleteSubscription
|
||||||
import li.songe.gkd.data.exportData
|
|
||||||
import li.songe.gkd.ui.destinations.CategoryPageDestination
|
import li.songe.gkd.ui.destinations.CategoryPageDestination
|
||||||
import li.songe.gkd.ui.destinations.GlobalRulePageDestination
|
import li.songe.gkd.ui.destinations.GlobalRulePageDestination
|
||||||
import li.songe.gkd.ui.destinations.SubsPageDestination
|
import li.songe.gkd.ui.destinations.SubsPageDestination
|
||||||
|
import li.songe.gkd.ui.home.HomeVm
|
||||||
import li.songe.gkd.util.LOCAL_SUBS_ID
|
import li.songe.gkd.util.LOCAL_SUBS_ID
|
||||||
import li.songe.gkd.util.LocalMainViewModel
|
import li.songe.gkd.util.LocalMainViewModel
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
|
@ -61,7 +60,7 @@ fun SubsItemCard(
|
||||||
subsItem: SubsItem,
|
subsItem: SubsItem,
|
||||||
subscription: RawSubscription?,
|
subscription: RawSubscription?,
|
||||||
index: Int,
|
index: Int,
|
||||||
vm: ViewModel,
|
vm: HomeVm,
|
||||||
isSelectedMode: Boolean,
|
isSelectedMode: Boolean,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
onCheckedChange: ((Boolean) -> Unit)? = null,
|
onCheckedChange: ((Boolean) -> Unit)? = null,
|
||||||
|
@ -192,7 +191,7 @@ private fun SubsMenuItem(
|
||||||
onExpandedChange: ((Boolean) -> Unit),
|
onExpandedChange: ((Boolean) -> Unit),
|
||||||
subItem: SubsItem,
|
subItem: SubsItem,
|
||||||
subscription: RawSubscription?,
|
subscription: RawSubscription?,
|
||||||
vm: ViewModel
|
vm: HomeVm
|
||||||
) {
|
) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
@ -243,7 +242,7 @@ private fun SubsMenuItem(
|
||||||
onClick = {
|
onClick = {
|
||||||
onExpandedChange(false)
|
onExpandedChange(false)
|
||||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
exportData(context, listOf(subItem.id))
|
vm.showShareDataIdsFlow.value = setOf(subItem.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -218,4 +218,6 @@ class HomeVm @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
val clickLogCountFlow =
|
val clickLogCountFlow =
|
||||||
DbSet.clickLogDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0)
|
DbSet.clickLogDao.count().stateIn(viewModelScope, SharingStarted.Eagerly, 0)
|
||||||
|
|
||||||
|
val showShareDataIdsFlow = MutableStateFlow<Set<Long>?>(null)
|
||||||
}
|
}
|
|
@ -66,6 +66,7 @@ import li.songe.gkd.util.checkUpdate
|
||||||
import li.songe.gkd.util.findOption
|
import li.songe.gkd.util.findOption
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
|
import li.songe.gkd.util.saveFileToDownloads
|
||||||
import li.songe.gkd.util.shareFile
|
import li.songe.gkd.util.shareFile
|
||||||
import li.songe.gkd.util.storeFlow
|
import li.songe.gkd.util.storeFlow
|
||||||
import li.songe.gkd.util.throttle
|
import li.songe.gkd.util.throttle
|
||||||
|
@ -149,7 +150,7 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
Text(
|
Text(
|
||||||
text = "调用系统分享", modifier = Modifier
|
text = "分享数据", modifier = Modifier
|
||||||
.clickable(onClick = throttle {
|
.clickable(onClick = throttle {
|
||||||
showShareLogDlg = false
|
showShareLogDlg = false
|
||||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
|
@ -159,6 +160,17 @@ fun useSettingsPage(): ScaffoldExt {
|
||||||
})
|
})
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
)
|
)
|
||||||
|
Text(
|
||||||
|
text = "保存到下载", modifier = Modifier
|
||||||
|
.clickable(onClick = throttle {
|
||||||
|
showShareLogDlg = false
|
||||||
|
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
|
val logZipFile = buildLogFile()
|
||||||
|
context.saveFileToDownloads(logZipFile)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(modifier)
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "生成链接(需科学上网)",
|
text = "生成链接(需科学上网)",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
@ -3,6 +3,7 @@ package li.songe.gkd.ui.home
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
@ -16,6 +17,7 @@ import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
|
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
@ -24,6 +26,7 @@ import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
@ -51,19 +54,20 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.dylanc.activityresult.launcher.launchForResult
|
import com.dylanc.activityresult.launcher.launchForResult
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import li.songe.gkd.MainActivity
|
||||||
import li.songe.gkd.data.Value
|
import li.songe.gkd.data.Value
|
||||||
import li.songe.gkd.data.deleteSubscription
|
import li.songe.gkd.data.deleteSubscription
|
||||||
import li.songe.gkd.data.exportData
|
import li.songe.gkd.data.exportData
|
||||||
import li.songe.gkd.data.importData
|
import li.songe.gkd.data.importData
|
||||||
import li.songe.gkd.db.DbSet
|
import li.songe.gkd.db.DbSet
|
||||||
import li.songe.gkd.ui.component.SubsItemCard
|
import li.songe.gkd.ui.component.SubsItemCard
|
||||||
import li.songe.gkd.ui.component.getResult
|
|
||||||
import li.songe.gkd.ui.component.waitResult
|
import li.songe.gkd.ui.component.waitResult
|
||||||
import li.songe.gkd.util.LOCAL_SUBS_ID
|
import li.songe.gkd.util.LOCAL_SUBS_ID
|
||||||
import li.songe.gkd.util.LocalLauncher
|
import li.songe.gkd.util.LocalLauncher
|
||||||
|
@ -72,9 +76,12 @@ import li.songe.gkd.util.checkSubsUpdate
|
||||||
import li.songe.gkd.util.isSafeUrl
|
import li.songe.gkd.util.isSafeUrl
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
|
import li.songe.gkd.util.saveFileToDownloads
|
||||||
|
import li.songe.gkd.util.shareFile
|
||||||
import li.songe.gkd.util.subsIdToRawFlow
|
import li.songe.gkd.util.subsIdToRawFlow
|
||||||
import li.songe.gkd.util.subsItemsFlow
|
import li.songe.gkd.util.subsItemsFlow
|
||||||
import li.songe.gkd.util.subsRefreshingFlow
|
import li.songe.gkd.util.subsRefreshingFlow
|
||||||
|
import li.songe.gkd.util.throttle
|
||||||
import li.songe.gkd.util.toast
|
import li.songe.gkd.util.toast
|
||||||
import sh.calvin.reorderable.ReorderableItem
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
@ -86,7 +93,6 @@ val subsNav = BottomNavItem(
|
||||||
@Composable
|
@Composable
|
||||||
fun useSubsManagePage(): ScaffoldExt {
|
fun useSubsManagePage(): ScaffoldExt {
|
||||||
val launcher = LocalLauncher.current
|
val launcher = LocalLauncher.current
|
||||||
val context = LocalContext.current
|
|
||||||
val mainVm = LocalMainViewModel.current
|
val mainVm = LocalMainViewModel.current
|
||||||
|
|
||||||
val vm = hiltViewModel<HomeVm>()
|
val vm = hiltViewModel<HomeVm>()
|
||||||
|
@ -171,6 +177,8 @@ fun useSubsManagePage(): ScaffoldExt {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShareDataDialog(vm)
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
return ScaffoldExt(
|
return ScaffoldExt(
|
||||||
navItem = subsNav,
|
navItem = subsNav,
|
||||||
|
@ -205,11 +213,10 @@ fun useSubsManagePage(): ScaffoldExt {
|
||||||
}
|
}
|
||||||
if (canDeleteIds.isNotEmpty()) {
|
if (canDeleteIds.isNotEmpty()) {
|
||||||
IconButton(onClick = vm.viewModelScope.launchAsFn {
|
IconButton(onClick = vm.viewModelScope.launchAsFn {
|
||||||
val result = mainVm.dialogFlow.getResult(
|
mainVm.dialogFlow.waitResult(
|
||||||
title = "删除订阅",
|
title = "删除订阅",
|
||||||
text = "是否删除所选 ${canDeleteIds.size} 个订阅?\n\n注: 不包含本地订阅",
|
text = "是否删除所选 ${canDeleteIds.size} 个订阅?\n\n注: 不包含本地订阅",
|
||||||
)
|
)
|
||||||
if (!result) return@launchAsFn
|
|
||||||
deleteSubscription(*canDeleteIds.toLongArray())
|
deleteSubscription(*canDeleteIds.toLongArray())
|
||||||
selectedIds = selectedIds - canDeleteIds
|
selectedIds = selectedIds - canDeleteIds
|
||||||
if (selectedIds.size == canDeleteIds.size) {
|
if (selectedIds.size == canDeleteIds.size) {
|
||||||
|
@ -223,7 +230,7 @@ fun useSubsManagePage(): ScaffoldExt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconButton(onClick = vm.viewModelScope.launchAsFn(Dispatchers.IO) {
|
IconButton(onClick = vm.viewModelScope.launchAsFn(Dispatchers.IO) {
|
||||||
exportData(context, selectedIds)
|
vm.showShareDataIdsFlow.value = selectedIds
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Share,
|
imageVector = Icons.Default.Share,
|
||||||
|
@ -425,4 +432,47 @@ fun useSubsManagePage(): ScaffoldExt {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShareDataDialog(vm: HomeVm) {
|
||||||
|
val context = LocalContext.current as MainActivity
|
||||||
|
val showShareDataIds = vm.showShareDataIdsFlow.collectAsState().value
|
||||||
|
if (showShareDataIds != null) {
|
||||||
|
Dialog(onDismissRequest = { vm.showShareDataIdsFlow.value = null }) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
) {
|
||||||
|
val modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
Text(
|
||||||
|
text = "分享数据", modifier = Modifier
|
||||||
|
.clickable(onClick = throttle {
|
||||||
|
vm.showShareDataIdsFlow.value = null
|
||||||
|
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
|
val file = exportData(showShareDataIds)
|
||||||
|
context.shareFile(file, "分享数据文件")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(modifier)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "保存到下载",
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = throttle {
|
||||||
|
vm.showShareDataIdsFlow.value = null
|
||||||
|
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
|
val file = exportData(showShareDataIds)
|
||||||
|
context.saveFileToDownloads(file)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(modifier)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,23 @@
|
||||||
package li.songe.gkd.util
|
package li.songe.gkd.util
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.blankj.utilcode.util.LogUtils
|
import com.blankj.utilcode.util.LogUtils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import li.songe.gkd.MainActivity
|
||||||
|
import li.songe.gkd.permission.canWriteExternalStorage
|
||||||
|
import li.songe.gkd.permission.requiredPermission
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun Context.shareFile(file: File, tile: String) {
|
fun Context.shareFile(file: File, title: String) {
|
||||||
val uri = FileProvider.getUriForFile(
|
val uri = FileProvider.getUriForFile(
|
||||||
this, "${packageName}.provider", file
|
this, "${packageName}.provider", file
|
||||||
)
|
)
|
||||||
|
@ -21,11 +30,36 @@ fun Context.shareFile(file: File, tile: String) {
|
||||||
}
|
}
|
||||||
tryStartActivity(
|
tryStartActivity(
|
||||||
Intent.createChooser(
|
Intent.createChooser(
|
||||||
intent, tile
|
intent, title
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun MainActivity.saveFileToDownloads(file: File) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
requiredPermission(this, canWriteExternalStorage)
|
||||||
|
val targetFile = File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
file.name
|
||||||
|
)
|
||||||
|
targetFile.writeBytes(file.readBytes())
|
||||||
|
} else {
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, file.name)
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
||||||
|
?: error("创建URI失败")
|
||||||
|
contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||||
|
outputStream.write(file.readBytes())
|
||||||
|
outputStream.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toast("已保存 ${file.name} 到下载")
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.tryStartActivity(intent: Intent) {
|
fun Context.tryStartActivity(intent: Intent) {
|
||||||
try {
|
try {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user