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 }