mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 13:00:17 +02:00
feat: file imports
This commit is contained in:
parent
7f5a10cce5
commit
7d5c053f21
@ -9,6 +9,7 @@ import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
|
|||||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||||
import me.rhunk.snapenhance.common.util.ktx.toParcelFileDescriptor
|
import me.rhunk.snapenhance.common.util.ktx.toParcelFileDescriptor
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
|
||||||
class LocalFileHandle(
|
class LocalFileHandle(
|
||||||
@ -39,7 +40,7 @@ class AssetFileHandle(
|
|||||||
return runCatching {
|
return runCatching {
|
||||||
context.androidContext.assets.open(assetPath).toParcelFileDescriptor(context.coroutineScope)
|
context.androidContext.assets.open(assetPath).toParcelFileDescriptor(context.coroutineScope)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
AbstractLogger.directError("Failed to open asset handle: ${it.message}", it)
|
context.log.error("Failed to open asset handle: ${it.message}", it)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +49,10 @@ class AssetFileHandle(
|
|||||||
class RemoteFileHandleManager(
|
class RemoteFileHandleManager(
|
||||||
private val context: RemoteSideContext
|
private val context: RemoteSideContext
|
||||||
): FileHandleManager.Stub() {
|
): FileHandleManager.Stub() {
|
||||||
|
private val userImportFolder = File(context.androidContext.filesDir, "user_imports").apply {
|
||||||
|
mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getFileHandle(scope: String, name: String): FileHandle? {
|
override fun getFileHandle(scope: String, name: String): FileHandle? {
|
||||||
val fileHandleScope = FileHandleScope.fromValue(scope) ?: run {
|
val fileHandleScope = FileHandleScope.fromValue(scope) ?: run {
|
||||||
context.log.error("invalid file handle scope: $scope", "FileHandleManager")
|
context.log.error("invalid file handle scope: $scope", "FileHandleManager")
|
||||||
@ -81,7 +86,43 @@ class RemoteFileHandleManager(
|
|||||||
"lang/$foundLocale.json"
|
"lang/$foundLocale.json"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
FileHandleScope.USER_IMPORT -> {
|
||||||
|
return LocalFileHandle(
|
||||||
|
File(userImportFolder, name.substringAfterLast("/"))
|
||||||
|
)
|
||||||
|
}
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getStoredFiles(): List<File> {
|
||||||
|
return userImportFolder.listFiles()?.toList()?.sortedBy { -it.lastModified() } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFileInfo(name: String): Pair<Long, Long>? {
|
||||||
|
return runCatching {
|
||||||
|
val file = File(userImportFolder, name)
|
||||||
|
file.length() to file.lastModified()
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to get file info: ${it.message}", it)
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importFile(name: String, block: OutputStream.() -> Unit): Boolean {
|
||||||
|
return runCatching {
|
||||||
|
val file = File(userImportFolder, name)
|
||||||
|
file.outputStream().use(block)
|
||||||
|
true
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to import file: ${it.message}", it)
|
||||||
|
}.getOrDefault(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteFile(name: String): Boolean {
|
||||||
|
return runCatching {
|
||||||
|
File(userImportFolder, name).delete()
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to delete file: ${it.message}", it)
|
||||||
|
}.isSuccess
|
||||||
|
}
|
||||||
}
|
}
|
@ -15,6 +15,7 @@ import androidx.navigation.NavDestination.Companion.hierarchy
|
|||||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
import me.rhunk.snapenhance.RemoteSideContext
|
||||||
|
import me.rhunk.snapenhance.ui.manager.pages.FileImportsRoot
|
||||||
import me.rhunk.snapenhance.ui.manager.pages.LoggerHistoryRoot
|
import me.rhunk.snapenhance.ui.manager.pages.LoggerHistoryRoot
|
||||||
import me.rhunk.snapenhance.ui.manager.pages.TasksRoot
|
import me.rhunk.snapenhance.ui.manager.pages.TasksRoot
|
||||||
import me.rhunk.snapenhance.ui.manager.pages.features.FeaturesRoot
|
import me.rhunk.snapenhance.ui.manager.pages.features.FeaturesRoot
|
||||||
@ -58,6 +59,7 @@ class Routes(
|
|||||||
val friendTracker = route(RouteInfo("friend_tracker"), FriendTrackerManagerRoot()).parent(home)
|
val friendTracker = route(RouteInfo("friend_tracker"), FriendTrackerManagerRoot()).parent(home)
|
||||||
val editRule = route(RouteInfo("edit_rule/?rule_id={rule_id}"), EditRule())
|
val editRule = route(RouteInfo("edit_rule/?rule_id={rule_id}"), EditRule())
|
||||||
|
|
||||||
|
val fileImports = route(RouteInfo("file_imports"), FileImportsRoot()).parent(home)
|
||||||
val social = route(RouteInfo("social", icon = Icons.Default.Group, primary = true), SocialRoot())
|
val social = route(RouteInfo("social", icon = Icons.Default.Group, primary = true), SocialRoot())
|
||||||
val manageScope = route(RouteInfo("manage_scope/?scope={scope}&id={id}"), ManageScope()).parent(social)
|
val manageScope = route(RouteInfo("manage_scope/?scope={scope}&id={id}"), ManageScope()).parent(social)
|
||||||
val messagingPreview = route(RouteInfo("messaging_preview/?scope={scope}&id={id}"), MessagingPreview()).parent(social)
|
val messagingPreview = route(RouteInfo("messaging_preview/?scope={scope}&id={id}"), MessagingPreview()).parent(social)
|
||||||
|
@ -0,0 +1,158 @@
|
|||||||
|
package me.rhunk.snapenhance.ui.manager.pages
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.text.format.Formatter
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.AttachFile
|
||||||
|
import androidx.compose.material.icons.filled.DeleteOutline
|
||||||
|
import androidx.compose.material.icons.filled.Upload
|
||||||
|
import androidx.compose.material3.ElevatedCard
|
||||||
|
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import androidx.navigation.NavBackStackEntry
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import me.rhunk.snapenhance.common.ui.AsyncUpdateDispatcher
|
||||||
|
import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState
|
||||||
|
import me.rhunk.snapenhance.common.ui.rememberAsyncMutableStateList
|
||||||
|
import me.rhunk.snapenhance.ui.manager.Routes
|
||||||
|
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
||||||
|
import me.rhunk.snapenhance.ui.util.openFile
|
||||||
|
import java.text.DateFormat
|
||||||
|
|
||||||
|
class FileImportsRoot: Routes.Route() {
|
||||||
|
private lateinit var activityLauncherHelper: ActivityLauncherHelper
|
||||||
|
private val reloadDispatcher = AsyncUpdateDispatcher()
|
||||||
|
|
||||||
|
override val init: () -> Unit = {
|
||||||
|
activityLauncherHelper = ActivityLauncherHelper(context.activity!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val floatingActionButton: @Composable () -> Unit = {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
Row {
|
||||||
|
ExtendedFloatingActionButton(
|
||||||
|
icon = {
|
||||||
|
Icon(Icons.Default.Upload, contentDescription = null)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(translation["import_file_button"])
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
context.coroutineScope.launch {
|
||||||
|
activityLauncherHelper.openFile { filePath ->
|
||||||
|
val fileUri = Uri.parse(filePath)
|
||||||
|
runCatching {
|
||||||
|
DocumentFile.fromSingleUri(context.activity!!, fileUri)?.let { file ->
|
||||||
|
if (!file.exists()) {
|
||||||
|
context.shortToast(translation["file_not_found"])
|
||||||
|
return@openFile
|
||||||
|
}
|
||||||
|
context.fileHandleManager.importFile(file.name!!) {
|
||||||
|
context.androidContext.contentResolver.openInputStream(fileUri)?.use { inputStream ->
|
||||||
|
inputStream.copyTo(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to import file", it)
|
||||||
|
context.shortToast(translation.format("file_import_failed", "error" to it.message.toString()))
|
||||||
|
}.onSuccess {
|
||||||
|
context.shortToast(translation["file_imported"])
|
||||||
|
coroutineScope.launch {
|
||||||
|
reloadDispatcher.dispatch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val content: @Composable (NavBackStackEntry) -> Unit = {
|
||||||
|
val files = rememberAsyncMutableStateList(defaultValue = listOf(), updateDispatcher = reloadDispatcher) {
|
||||||
|
context.fileHandleManager.getStoredFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(2.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = translation["no_files_hint"],
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Light
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(files, key = { it }) { file ->
|
||||||
|
ElevatedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
val fileInfo by rememberAsyncMutableState(defaultValue = null) {
|
||||||
|
context.fileHandleManager.getFileInfo(file.name)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.AttachFile, contentDescription = null, modifier = Modifier.padding(5.dp))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f).padding(8.dp),
|
||||||
|
) {
|
||||||
|
Text(text = file.name, fontWeight = FontWeight.Bold, fontSize = 18.sp, lineHeight = 20.sp)
|
||||||
|
fileInfo?.let { (size, lastModified) ->
|
||||||
|
Text(text = "${Formatter.formatFileSize(context.androidContext, size)} - ${DateFormat.getDateTimeInstance().format(lastModified)}", lineHeight = 15.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
|
) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
context.coroutineScope.launch {
|
||||||
|
if (context.fileHandleManager.deleteFile(file.name)) {
|
||||||
|
files.remove(file)
|
||||||
|
} else {
|
||||||
|
context.shortToast(translation["file_delete_failed"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.DeleteOutline, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(100.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,10 +14,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.filled.FolderOpen
|
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
|
||||||
import androidx.compose.material.icons.filled.Search
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -40,6 +37,7 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.common.config.*
|
import me.rhunk.snapenhance.common.config.*
|
||||||
|
import me.rhunk.snapenhance.common.ui.rememberAsyncMutableStateList
|
||||||
import me.rhunk.snapenhance.ui.manager.MainActivity
|
import me.rhunk.snapenhance.ui.manager.MainActivity
|
||||||
import me.rhunk.snapenhance.ui.manager.Routes
|
import me.rhunk.snapenhance.ui.manager.Routes
|
||||||
import me.rhunk.snapenhance.ui.util.*
|
import me.rhunk.snapenhance.ui.util.*
|
||||||
@ -153,6 +151,71 @@ class FeaturesRoot : Routes.Route() {
|
|||||||
|
|
||||||
val propertyValue = property.value
|
val propertyValue = property.value
|
||||||
|
|
||||||
|
if (property.key.params.flags.contains(ConfigFlag.USER_IMPORT)) {
|
||||||
|
registerDialogOnClickCallback()
|
||||||
|
dialogComposable = {
|
||||||
|
val files = rememberAsyncMutableStateList(defaultValue = listOf()) {
|
||||||
|
context.fileHandleManager.getStoredFiles()
|
||||||
|
}
|
||||||
|
var selectedFile by remember(files.size) { mutableStateOf(files.firstOrNull { it.name == propertyValue.getNullable() }?.name) }
|
||||||
|
|
||||||
|
Card(
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(4.dp),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = context.translation["manager.dialogs.file_imports.settings_select_file_hint"],
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = context.translation["manager.dialogs.file_imports.no_files_settings_hint"],
|
||||||
|
fontSize = 16.sp,
|
||||||
|
modifier = Modifier.padding(top = 10.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(files, key = { it.name }) { file ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
selectedFile = if (selectedFile == file.name) null else file.name
|
||||||
|
propertyValue.setAny(selectedFile)
|
||||||
|
}.padding(5.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(Icons.Filled.AttachFile, contentDescription = null, modifier = Modifier.padding(5.dp))
|
||||||
|
Text(
|
||||||
|
text = file.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(3.dp)
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 16.sp
|
||||||
|
)
|
||||||
|
if (selectedFile == file.name) {
|
||||||
|
Icon(Icons.Filled.Check, contentDescription = null, modifier = Modifier.padding(5.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(Icons.Filled.AttachFile, contentDescription = null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (property.key.params.flags.contains(ConfigFlag.FOLDER)) {
|
if (property.key.params.flags.contains(ConfigFlag.FOLDER)) {
|
||||||
IconButton(onClick = registerClickCallback {
|
IconButton(onClick = registerClickCallback {
|
||||||
activityLauncher {
|
activityLauncher {
|
||||||
|
@ -8,11 +8,7 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.Help
|
import androidx.compose.material.icons.automirrored.filled.Help
|
||||||
import androidx.compose.material.icons.filled.BugReport
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.filled.History
|
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
|
||||||
import androidx.compose.material.icons.filled.PersonSearch
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -61,6 +57,9 @@ class HomeRoot : Routes.Route() {
|
|||||||
|
|
||||||
private val cards by lazy {
|
private val cards by lazy {
|
||||||
mapOf(
|
mapOf(
|
||||||
|
("File Imports" to Icons.Default.FolderOpen) to {
|
||||||
|
routes.fileImports.navigateReset()
|
||||||
|
},
|
||||||
("Friend Tracker" to Icons.Default.PersonSearch) to {
|
("Friend Tracker" to Icons.Default.PersonSearch) to {
|
||||||
routes.friendTracker.navigateReset()
|
routes.friendTracker.navigateReset()
|
||||||
},
|
},
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"logged_stories": "Logged Stories",
|
"logged_stories": "Logged Stories",
|
||||||
"friend_tracker": "Friend Tracker",
|
"friend_tracker": "Friend Tracker",
|
||||||
"edit_rule": "Edit Rule",
|
"edit_rule": "Edit Rule",
|
||||||
|
"file_imports": "File Imports",
|
||||||
"social": "Social",
|
"social": "Social",
|
||||||
"manage_scope": "Manage Scope",
|
"manage_scope": "Manage Scope",
|
||||||
"messaging_preview": "Preview",
|
"messaging_preview": "Preview",
|
||||||
@ -131,6 +132,14 @@
|
|||||||
"message_parse_failed": "Failed to parse message",
|
"message_parse_failed": "Failed to parse message",
|
||||||
"unknown_sender": "Unknown Sender",
|
"unknown_sender": "Unknown Sender",
|
||||||
"download_attachment_failed_toast": "Failed to download attachment"
|
"download_attachment_failed_toast": "Failed to download attachment"
|
||||||
|
},
|
||||||
|
"file_imports": {
|
||||||
|
"import_file_button": "Import File",
|
||||||
|
"file_not_found": "File not found",
|
||||||
|
"file_import_failed": "Failed to import file: {error}",
|
||||||
|
"file_imported": "File imported successfully",
|
||||||
|
"file_delete_failed": "Failed to delete file",
|
||||||
|
"no_files_hint": "Here you can import files for use in Snapchat. Press the button below to import a file."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dialogs": {
|
"dialogs": {
|
||||||
@ -153,6 +162,10 @@
|
|||||||
"messaging_action": {
|
"messaging_action": {
|
||||||
"title": "Choose content types to process",
|
"title": "Choose content types to process",
|
||||||
"select_all_button": "Select All"
|
"select_all_button": "Select All"
|
||||||
|
},
|
||||||
|
"file_imports": {
|
||||||
|
"no_files_settings_hint": "No files found. Make sure you have imported the required files in the File Imports section",
|
||||||
|
"settings_select_file_hint": "Select an imported file"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,8 @@ enum class FileHandleScope(
|
|||||||
val key: String
|
val key: String
|
||||||
) {
|
) {
|
||||||
INTERNAL("internal"),
|
INTERNAL("internal"),
|
||||||
LOCALE("locale");
|
LOCALE("locale"),
|
||||||
|
USER_IMPORT("user_import");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromValue(name: String): FileHandleScope? = entries.find { it.key == name }
|
fun fromValue(name: String): FileHandleScope? = entries.find { it.key == name }
|
||||||
|
@ -12,24 +12,26 @@ data class PropertyPair<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class FeatureNotice(
|
enum class FeatureNotice(
|
||||||
val id: Int,
|
|
||||||
val key: String
|
val key: String
|
||||||
) {
|
) {
|
||||||
UNSTABLE(0b0001, "unstable"),
|
UNSTABLE("unstable"),
|
||||||
BAN_RISK(0b0010, "ban_risk"),
|
BAN_RISK("ban_risk"),
|
||||||
INTERNAL_BEHAVIOR(0b0100, "internal_behavior"),
|
INTERNAL_BEHAVIOR("internal_behavior"),
|
||||||
REQUIRE_NATIVE_HOOKS(0b1000, "require_native_hooks"),
|
REQUIRE_NATIVE_HOOKS("require_native_hooks");
|
||||||
|
|
||||||
|
val id get() = 1 shl ordinal
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ConfigFlag(
|
enum class ConfigFlag {
|
||||||
val id: Int
|
NO_TRANSLATE,
|
||||||
) {
|
HIDDEN,
|
||||||
NO_TRANSLATE(0b000001),
|
FOLDER,
|
||||||
HIDDEN(0b000010),
|
USER_IMPORT,
|
||||||
FOLDER(0b000100),
|
NO_DISABLE_KEY,
|
||||||
NO_DISABLE_KEY(0b001000),
|
REQUIRE_RESTART,
|
||||||
REQUIRE_RESTART(0b010000),
|
REQUIRE_CLEAN_CACHE;
|
||||||
REQUIRE_CLEAN_CACHE(0b100000)
|
|
||||||
|
val id = 1 shl ordinal
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigParams(
|
class ConfigParams(
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package me.rhunk.snapenhance.core.util.ktx
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.ParcelFileDescriptor
|
||||||
|
import me.rhunk.snapenhance.bridge.storage.FileHandleManager
|
||||||
|
import me.rhunk.snapenhance.common.bridge.FileHandleScope
|
||||||
|
import me.rhunk.snapenhance.common.util.ktx.longHashCode
|
||||||
|
import me.rhunk.snapenhance.core.ModContext
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
fun FileHandleManager.getFileHandleLocalPath(
|
||||||
|
context: ModContext,
|
||||||
|
scope: FileHandleScope,
|
||||||
|
name: String,
|
||||||
|
fileUniqueIdentifier: String,
|
||||||
|
): String? {
|
||||||
|
return getFileHandle(scope.key, name)?.open(ParcelFileDescriptor.MODE_READ_ONLY)?.use { pfd ->
|
||||||
|
val cacheFile = context.androidContext.cacheDir.resolve((fileUniqueIdentifier + Build.FINGERPRINT).longHashCode().absoluteValue.toString(16))
|
||||||
|
if (!cacheFile.exists() || pfd.statSize != cacheFile.length()) {
|
||||||
|
FileOutputStream(cacheFile).use { output ->
|
||||||
|
ParcelFileDescriptor.AutoCloseInputStream(pfd).use { input ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cacheFile.absolutePath
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user