Skip to content

Commit

Permalink
🔀 Merge pull request #13 from vinceglb/save-file-dialog
Browse files Browse the repository at this point in the history
Save file dialog
  • Loading branch information
vinceglb authored May 1, 2024
2 parents e2f5d8a + 5331394 commit 8ffd000
Show file tree
Hide file tree
Showing 33 changed files with 946 additions and 569 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
agp = "8.3.2"
agp = "8.4.0"
android-activity-ktx = "1.9.0"
android-compose = "1.9.0"
coilCompose = "3.0.0-alpha06"
Expand Down
7 changes: 3 additions & 4 deletions picker-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ kotlin {
// Compose
implementation(compose.runtime)

// Coroutines
implementation(libs.kotlinx.coroutines.core)

// Picker Core
api(projects.pickerCore)
}
Expand All @@ -67,10 +70,6 @@ kotlin {

val nonAndroidMain by creating {
dependsOn(commonMain.get())

dependencies {
implementation(libs.kotlinx.coroutines.core)
}
}

nativeMain.get().dependsOn(nonAndroidMain)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,96 +1,15 @@
package io.github.vinceglb.picker.compose

import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTree
import androidx.activity.result.contract.ActivityResultContracts.OpenMultipleDocuments
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import io.github.vinceglb.picker.core.Picker
import io.github.vinceglb.picker.core.PickerSelectionMode
import io.github.vinceglb.picker.core.PickerSelectionMode.Directory
import io.github.vinceglb.picker.core.PickerSelectionMode.MultipleFiles
import io.github.vinceglb.picker.core.PickerSelectionMode.SingleFile
import io.github.vinceglb.picker.core.PlatformDirectory
import io.github.vinceglb.picker.core.PlatformFile

@Composable
public actual fun <Out> rememberPickerLauncher(
mode: PickerSelectionMode<Out>,
title: String?,
initialDirectory: String?,
onResult: (Out?) -> Unit,
): PickerResultLauncher {
// Get context
val context = LocalContext.current

// Keep track of the current mode, initialDirectory and onResult listener
val currentMode by rememberUpdatedState(mode)
val currentInitialDirectory by rememberUpdatedState(initialDirectory)
val currentOnResult by rememberUpdatedState(onResult)

// Create Picker launcher based on mode
val launcher = when (val currentModeValue = currentMode) {
is SingleFile -> {
// Create Android launcher
@Suppress("UNCHECKED_CAST")
val launcher = rememberLauncherForActivityResult(OpenDocument()) { uri ->
val platformFile = uri?.let { PlatformFile(it, context) }
currentOnResult(platformFile as Out?)
}

remember {
// Get mime types
val mimeTypes = Picker.getMimeType(currentModeValue.extensions)

// Return Picker launcher
PickerResultLauncher { launcher.launch(mimeTypes) }
}
}

is MultipleFiles -> {
// Create Android launcher
@Suppress("UNCHECKED_CAST")
val launcher = rememberLauncherForActivityResult(OpenMultipleDocuments()) { uris ->
val platformFiles = uris
.takeIf { it.isNotEmpty() }
?.map { uri -> PlatformFile(uri, context) }

currentOnResult(platformFiles as Out?)
}

remember {
// Get mime types
val mimeTypes = Picker.getMimeType(currentModeValue.extensions)

// Return Picker launcher
PickerResultLauncher { launcher.launch(mimeTypes) }
}
}

is Directory -> {
// Create Android launcher
@Suppress("UNCHECKED_CAST")
val launcher = rememberLauncherForActivityResult(OpenDocumentTree()) { uri ->
val platformDirectory = uri?.let { PlatformDirectory(it) }
currentOnResult(platformDirectory as Out?)
}

remember {
// Convert initialDirectory to Uri
val initialPath = currentInitialDirectory?.let { Uri.parse(it) }

// Return Picker launcher
PickerResultLauncher { launcher.launch(initialPath) }
}
}

else -> throw IllegalArgumentException("Unsupported mode: $currentModeValue")
internal actual fun InitPicker() {
val componentActivity = LocalContext.current as ComponentActivity
LaunchedEffect(Unit) {
Picker.init(componentActivity)
}

return launcher
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,87 @@
package io.github.vinceglb.picker.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import io.github.vinceglb.picker.core.Picker
import io.github.vinceglb.picker.core.PickerSelectionMode
import io.github.vinceglb.picker.core.PlatformFile
import kotlinx.coroutines.launch

@Composable
public expect fun <Out> rememberPickerLauncher(
public fun <Out> rememberPickerLauncher(
mode: PickerSelectionMode<Out>,
title: String? = null,
initialDirectory: String? = null,
onResult: (Out?) -> Unit,
): PickerResultLauncher
): PickerResultLauncher {
// Init picker
InitPicker()

// Coroutine
val coroutineScope = rememberCoroutineScope()

// Updated state
val currentMode by rememberUpdatedState(mode)
val currentTitle by rememberUpdatedState(title)
val currentInitialDirectory by rememberUpdatedState(initialDirectory)
val currentOnResult by rememberUpdatedState(onResult)

// Picker
val picker = remember { Picker }

// Picker launcher
val returnedLauncher = remember {
PickerResultLauncher {
coroutineScope.launch {
val result = picker.pick(
mode = currentMode,
title = currentTitle,
initialDirectory = currentInitialDirectory,
)
currentOnResult(result)
}
}
}

return returnedLauncher
}

@Composable
public fun rememberSaverLauncher(
onResult: (PlatformFile?) -> Unit
): SaverResultLauncher {
// Init picker
InitPicker()

// Coroutine
val coroutineScope = rememberCoroutineScope()

// Updated state
val currentOnResult by rememberUpdatedState(onResult)

// Picker
val picker = remember { Picker }

// Picker launcher
val returnedLauncher = remember {
SaverResultLauncher { bytes, baseName, extension, initialDirectory ->
coroutineScope.launch {
val result = picker.save(
bytes = bytes,
baseName = baseName,
extension = extension,
initialDirectory = initialDirectory,
)
currentOnResult(result)
}
}
}

return returnedLauncher
}

@Composable
internal expect fun InitPicker()
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,21 @@ public class PickerResultLauncher(
onLaunch()
}
}

public class SaverResultLauncher(
private val onLaunch: (
bytes: ByteArray,
baseName: String,
extension: String,
initialDirectory: String?,
) -> Unit,
) {
public fun launch(
bytes: ByteArray,
baseName: String = "file",
extension: String,
initialDirectory: String? = null,
) {
onLaunch(bytes, baseName, extension, initialDirectory)
}
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,6 @@
package io.github.vinceglb.picker.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import io.github.vinceglb.picker.core.Picker
import io.github.vinceglb.picker.core.PickerSelectionMode
import kotlinx.coroutines.launch

@Composable
public actual fun <Out> rememberPickerLauncher(
mode: PickerSelectionMode<Out>,
title: String?,
initialDirectory: String?,
onResult: (Out?) -> Unit
): PickerResultLauncher {
// Coroutine
val coroutineScope = rememberCoroutineScope()

// Updated state
val currentMode by rememberUpdatedState(mode)
val currentTitle by rememberUpdatedState(title)
val currentInitialDirectory by rememberUpdatedState(initialDirectory)
val currentOnResult by rememberUpdatedState(onResult)

// Picker
val picker = remember { Picker }

// Picker launcher
val returnedLauncher = remember {
PickerResultLauncher {
coroutineScope.launch {
val result = picker.pick(
mode = currentMode,
title = currentTitle,
initialDirectory = currentInitialDirectory,
)
currentOnResult(result)
}
}
}

return returnedLauncher
}
internal actual fun InitPicker() {}
Loading

0 comments on commit 8ffd000

Please sign in to comment.