FileImportsRoot.kt (6997B) - raw
1 package me.rhunk.snapenhance.ui.manager.pages 2 3 import android.net.Uri 4 import android.text.format.Formatter 5 import androidx.compose.foundation.layout.* 6 import androidx.compose.foundation.lazy.LazyColumn 7 import androidx.compose.foundation.lazy.items 8 import androidx.compose.material.icons.Icons 9 import androidx.compose.material.icons.filled.AttachFile 10 import androidx.compose.material.icons.filled.DeleteOutline 11 import androidx.compose.material.icons.filled.Upload 12 import androidx.compose.material3.ElevatedCard 13 import androidx.compose.material3.ExtendedFloatingActionButton 14 import androidx.compose.material3.Icon 15 import androidx.compose.material3.IconButton 16 import androidx.compose.material3.Text 17 import androidx.compose.runtime.Composable 18 import androidx.compose.runtime.getValue 19 import androidx.compose.runtime.rememberCoroutineScope 20 import androidx.compose.ui.Alignment 21 import androidx.compose.ui.Modifier 22 import androidx.compose.ui.text.font.FontWeight 23 import androidx.compose.ui.text.style.TextAlign 24 import androidx.compose.ui.unit.dp 25 import androidx.compose.ui.unit.sp 26 import androidx.documentfile.provider.DocumentFile 27 import androidx.navigation.NavBackStackEntry 28 import kotlinx.coroutines.launch 29 import me.rhunk.snapenhance.common.ui.AsyncUpdateDispatcher 30 import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState 31 import me.rhunk.snapenhance.common.ui.rememberAsyncMutableStateList 32 import me.rhunk.snapenhance.ui.manager.Routes 33 import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper 34 import me.rhunk.snapenhance.ui.util.openFile 35 import java.text.DateFormat 36 37 class FileImportsRoot: Routes.Route() { 38 private lateinit var activityLauncherHelper: ActivityLauncherHelper 39 private val reloadDispatcher = AsyncUpdateDispatcher() 40 41 override val init: () -> Unit = { 42 activityLauncherHelper = ActivityLauncherHelper(context.activity!!) 43 } 44 45 override val floatingActionButton: @Composable () -> Unit = { 46 val coroutineScope = rememberCoroutineScope() 47 Row { 48 ExtendedFloatingActionButton( 49 icon = { 50 Icon(Icons.Default.Upload, contentDescription = null) 51 }, 52 text = { 53 Text(translation["import_file_button"]) 54 }, 55 onClick = { 56 context.coroutineScope.launch { 57 activityLauncherHelper.openFile { filePath -> 58 val fileUri = Uri.parse(filePath) 59 runCatching { 60 DocumentFile.fromSingleUri(context.activity!!, fileUri)?.let { file -> 61 if (!file.exists()) { 62 context.shortToast(translation["file_not_found"]) 63 return@openFile 64 } 65 context.fileHandleManager.importFile(file.name!!) { 66 context.androidContext.contentResolver.openInputStream(fileUri)?.use { inputStream -> 67 inputStream.copyTo(this) 68 } 69 } 70 } 71 }.onFailure { 72 context.log.error("Failed to import file", it) 73 context.shortToast(translation.format("file_import_failed", "error" to it.message.toString())) 74 }.onSuccess { 75 context.shortToast(translation["file_imported"]) 76 coroutineScope.launch { 77 reloadDispatcher.dispatch() 78 } 79 } 80 } 81 } 82 }) 83 } 84 } 85 86 override val content: @Composable (NavBackStackEntry) -> Unit = { 87 val files = rememberAsyncMutableStateList(defaultValue = listOf(), updateDispatcher = reloadDispatcher) { 88 context.fileHandleManager.getStoredFiles() 89 } 90 91 LazyColumn( 92 modifier = Modifier 93 .fillMaxSize() 94 .padding(2.dp), 95 verticalArrangement = Arrangement.spacedBy(5.dp) 96 ) { 97 item { 98 if (files.isEmpty()) { 99 Text( 100 text = translation["no_files_hint"], 101 modifier = Modifier 102 .padding(8.dp) 103 .fillMaxWidth(), 104 textAlign = TextAlign.Center, 105 fontSize = 18.sp, 106 fontWeight = FontWeight.Light 107 ) 108 } 109 } 110 items(files, key = { it }) { file -> 111 ElevatedCard( 112 modifier = Modifier 113 .fillMaxWidth() 114 .padding(8.dp) 115 ) { 116 val fileInfo by rememberAsyncMutableState(defaultValue = null) { 117 context.fileHandleManager.getFileInfo(file.name) 118 } 119 Row( 120 modifier = Modifier 121 .padding(8.dp) 122 .fillMaxWidth(), 123 verticalAlignment = Alignment.CenterVertically 124 ) { 125 Icon(Icons.Default.AttachFile, contentDescription = null, modifier = Modifier.padding(5.dp)) 126 Column( 127 modifier = Modifier.weight(1f).padding(8.dp), 128 ) { 129 Text(text = file.name, fontWeight = FontWeight.Bold, fontSize = 18.sp, lineHeight = 20.sp) 130 fileInfo?.let { (size, lastModified) -> 131 Text(text = "${Formatter.formatFileSize(context.androidContext, size)} - ${DateFormat.getDateTimeInstance().format(lastModified)}", lineHeight = 15.sp) 132 } 133 } 134 135 Row( 136 horizontalArrangement = Arrangement.spacedBy(5.dp) 137 ) { 138 IconButton(onClick = { 139 context.coroutineScope.launch { 140 if (context.fileHandleManager.deleteFile(file.name)) { 141 files.remove(file) 142 } else { 143 context.shortToast(translation["file_delete_failed"]) 144 } 145 } 146 }) { 147 Icon(Icons.Default.DeleteOutline, contentDescription = null) 148 } 149 } 150 } 151 } 152 } 153 item { 154 Spacer(modifier = Modifier.height(100.dp)) 155 } 156 } 157 } 158 }