diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4f2ecd3d..b9ce70c9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -159,6 +159,16 @@ + + + + + + + + + + by mutableStateOf(emptyList()) companion object { lateinit var appContext: Context diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivity.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivity.kt index f45052e2..ba442662 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivity.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivity.kt @@ -36,6 +36,8 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.Velocity import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect +import android.content.Intent +import android.net.Uri import com.raival.compose.file.explorer.App.Companion.globalClass import com.raival.compose.file.explorer.R import com.raival.compose.file.explorer.base.BaseActivity @@ -115,6 +117,9 @@ class MainActivity : BaseActivity() { mainActivityManager.checkForUpdate() if (hasIntent()) { handleIntent() + if (mainActivityState.tabs.isEmpty()) { + mainActivityManager.loadStartupTabs() + } } else { if (mainActivityState.tabs.isEmpty()) { mainActivityManager.loadStartupTabs() @@ -343,17 +348,43 @@ class MainActivity : BaseActivity() { } private fun hasIntent(): Boolean { - return intent isNot null && intent!!.hasExtra(HOME_SCREEN_SHORTCUT_EXTRA_KEY) + if (intent == null) return false + val action = intent!!.action + return intent!!.hasExtra(HOME_SCREEN_SHORTCUT_EXTRA_KEY) + || action == Intent.ACTION_SEND + || action == Intent.ACTION_SEND_MULTIPLE } private fun handleIntent() { intent?.let { - if (it.hasExtra(HOME_SCREEN_SHORTCUT_EXTRA_KEY)) { - globalClass.mainActivityManager.jumpToFile( - file = LocalFileHolder(File(it.getStringExtra(HOME_SCREEN_SHORTCUT_EXTRA_KEY)!!)), - context = this - ) - intent = null + when (it.action) { + Intent.ACTION_SEND -> { + @Suppress("DEPRECATION") + val uri = it.getParcelableExtra(Intent.EXTRA_STREAM) + if (uri != null) { + globalClass.shareUris = listOf(uri) + globalClass.isShareMode = true + } + intent = null + } + Intent.ACTION_SEND_MULTIPLE -> { + @Suppress("DEPRECATION") + val uris = it.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + if (!uris.isNullOrEmpty()) { + globalClass.shareUris = uris + globalClass.isShareMode = true + } + intent = null + } + else -> { + if (it.hasExtra(HOME_SCREEN_SHORTCUT_EXTRA_KEY)) { + globalClass.mainActivityManager.jumpToFile( + file = LocalFileHolder(File(it.getStringExtra(HOME_SCREEN_SHORTCUT_EXTRA_KEY)!!)), + context = this + ) + intent = null + } + } } } } diff --git a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/ui/BottomOptionsBar.kt b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/ui/BottomOptionsBar.kt index 8beb07e7..235437e5 100644 --- a/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/ui/BottomOptionsBar.kt +++ b/app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/ui/BottomOptionsBar.kt @@ -5,7 +5,20 @@ import androidx.compose.animation.expandIn import androidx.compose.animation.shrinkOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import android.app.Activity +import android.provider.OpenableColumns import androidx.compose.foundation.clickable +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.rounded.SaveAlt +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextOverflow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -54,6 +67,92 @@ import com.raival.compose.file.explorer.screen.main.tab.files.task.CopyTask fun BottomOptionsBar(tab: FilesTab) { val state = tab.bottomOptionsBarState.collectAsState().value + // ── Share mode: Save here banner ────────────────────────────────────────── + if (globalClass.isShareMode && globalClass.shareUris.isNotEmpty()) { + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + var isSaving by remember { mutableStateOf(false) } + val fileLabel = if (globalClass.shareUris.size == 1) + getSharedFileName(context, globalClass.shareUris[0]) ?: context.getString(R.string.unknown_file) + else context.getString(R.string.shared_files_count, globalClass.shareUris.size) + + HorizontalDivider() + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = fileLabel, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + androidx.compose.material3.OutlinedButton( + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(6.dp), + onClick = { + globalClass.isShareMode = false + globalClass.shareUris = emptyList() + } + ) { + Text(text = stringResource(R.string.cancel)) + } + androidx.compose.material3.Button( + modifier = Modifier.weight(1f), + enabled = !isSaving && tab.activeFolder.canWrite, + shape = RoundedCornerShape(6.dp), + onClick = { + coroutineScope.launch(Dispatchers.IO) { + isSaving = true + val ok = saveSharedFilesToFolder(context, globalClass.shareUris, tab) + isSaving = false + if (ok) { + globalClass.isShareMode = false + globalClass.shareUris = emptyList() + globalClass.showMsg(R.string.file_saved_successfully) + tab.reloadFiles() + } else { + globalClass.showMsg(R.string.failed_to_save_file) + } + } + } + ) { + if (isSaving) { + androidx.compose.material3.CircularProgressIndicator( + modifier = Modifier.size(16.dp), + color = MaterialTheme.colorScheme.onPrimary, + strokeWidth = 2.dp + ) + Space(size = 8.dp) + Text(text = stringResource(R.string.saving_file)) + } else { + Icon( + modifier = Modifier.size(16.dp), + imageVector = Icons.Rounded.SaveAlt, + contentDescription = null + ) + Space(size = 8.dp) + Text(text = stringResource(R.string.save_here)) + } + } + } // end Row + } + } + // ───────────────────────────────────────────────────────────────────────── + AnimatedVisibility( visible = state.showQuickOptions && tab.selectedFiles.isNotEmpty(), enter = expandIn(expandFrom = Alignment.TopCenter) + slideInVertically( @@ -253,3 +352,49 @@ fun RowScope.BottomOptionsBarButton( view() } } + +private fun getSharedFileName(context: android.content.Context, uri: android.net.Uri): String? { + context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (idx >= 0) return cursor.getString(idx) + } + } + return uri.lastPathSegment +} + +private fun saveSharedFilesToFolder( + context: android.content.Context, + uris: List, + tab: FilesTab +): Boolean { + return try { + val localFolder = tab.activeFolder + as? com.raival.compose.file.explorer.screen.main.tab.files.holder.LocalFileHolder + ?: return false + val destDir = localFolder.file + if (!destDir.exists() || !destDir.isDirectory) return false + uris.forEach { uri -> + val originalName = getSharedFileName(context, uri) ?: "shared_${System.currentTimeMillis()}" + val destFile = getUniqueFile(destDir, originalName) + context.contentResolver.openInputStream(uri)?.use { input -> + destFile.outputStream().use { input.copyTo(it) } + } + } + true + } catch (e: Exception) { false } +} + +private fun getUniqueFile(dir: java.io.File, fileName: String): java.io.File { + val dot = fileName.lastIndexOf('.') + val name = if (dot != -1) fileName.substring(0, dot) else fileName + val ext = if (dot != -1) fileName.substring(dot) else "" + + var file = java.io.File(dir, fileName) + var counter = 1 + while (file.exists()) { + file = java.io.File(dir, "${name} Copy($counter)${ext}") + counter++ + } + return file +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 388efca7..a4a9550b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -453,4 +453,13 @@ Back navigation to close tabs Remember last session Confirm before exiting the app + Save File + Save here + Saving... + File saved successfully + Failed to save file + Destination folder + No files received + Unknown file + %d shared files