mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
feat(receive): add adaptive receive flow and document opening
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package com.vnidrop.app
|
||||
|
||||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
import com.vnidrop.app.feature.receive.ExternalInvitationController
|
||||
|
||||
fun MainViewController() = ComposeUIViewController { App(rememberIosAppDependencies()) }
|
||||
fun MainViewController(externalInvitations: ExternalInvitationController) =
|
||||
ComposeUIViewController { App(rememberIosAppDependencies(externalInvitations)) }
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vnidrop.app.core.rememberFileSystemService
|
||||
import com.vnidrop.app.notifications.IosLocalNotificationService
|
||||
import com.vnidrop.app.feature.receive.ExternalInvitationController
|
||||
import platform.Foundation.NSBundle
|
||||
import platform.Foundation.NSApplicationSupportDirectory
|
||||
import platform.Foundation.NSSearchPathForDirectoriesInDomains
|
||||
@@ -11,7 +12,7 @@ import platform.Foundation.NSUserDomainMask
|
||||
import platform.UIKit.UIDevice
|
||||
|
||||
@Composable
|
||||
fun rememberIosAppDependencies(): AppDependencies {
|
||||
fun rememberIosAppDependencies(externalInvitations: ExternalInvitationController): AppDependencies {
|
||||
val fileSystemService = rememberFileSystemService()
|
||||
return remember(fileSystemService) {
|
||||
val device = UIDevice.currentDevice
|
||||
@@ -25,6 +26,7 @@ fun rememberIosAppDependencies(): AppDependencies {
|
||||
deviceInfoProvider = IosDeviceInfoProvider(device),
|
||||
fileSystemService = fileSystemService,
|
||||
localNotificationService = IosLocalNotificationService(),
|
||||
externalInvitations = externalInvitations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.vnidrop.app.feature.receive
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import kotlinx.cinterop.ExperimentalForeignApi
|
||||
import kotlinx.cinterop.readBytes
|
||||
import platform.Foundation.NSFileManager
|
||||
import platform.Foundation.NSURL
|
||||
import platform.UIKit.UIApplication
|
||||
import platform.UIKit.UIDocumentPickerDelegateProtocol
|
||||
import platform.UIKit.UIDocumentPickerViewController
|
||||
import platform.UIKit.UIModalPresentationFormSheet
|
||||
import platform.UniformTypeIdentifiers.UTTypeData
|
||||
import platform.darwin.NSObject
|
||||
|
||||
private var retainedInvitationDelegate: InvitationDocumentDelegate? = null
|
||||
|
||||
@Composable
|
||||
actual fun rememberReceiveInvitationActions(): ReceiveInvitationActions = remember {
|
||||
object : ReceiveInvitationActions {
|
||||
override val fileAvailability = ReceiveMethodAvailability.Available
|
||||
override val qrAvailability = ReceiveMethodAvailability.Unavailable
|
||||
override val nfcAvailability = ReceiveMethodAvailability.Unavailable
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
override fun pickInvitation(onResult: (Result<String>) -> Unit) {
|
||||
val presenter = UIApplication.sharedApplication.keyWindow?.rootViewController
|
||||
?: return onResult(Result.failure(IllegalStateException("Could not find an iOS view controller")))
|
||||
val picker = UIDocumentPickerViewController(forOpeningContentTypes = listOf(UTTypeData), asCopy = true)
|
||||
val delegate = InvitationDocumentDelegate(onResult)
|
||||
retainedInvitationDelegate = delegate
|
||||
picker.delegate = delegate
|
||||
picker.modalPresentationStyle = UIModalPresentationFormSheet
|
||||
presenter.presentViewController(picker, animated = true, completion = null)
|
||||
}
|
||||
|
||||
override fun scanQrCode(onResult: (Result<String>) -> Unit) =
|
||||
onResult(Result.failure(UnsupportedOperationException("QR scanning is not enabled for this iOS build")))
|
||||
|
||||
override fun readNfcInvitation(onResult: (Result<String>) -> Unit) =
|
||||
onResult(Result.failure(UnsupportedOperationException("NFC reading is not enabled for this iOS build")))
|
||||
|
||||
override fun cancel() = Unit
|
||||
}
|
||||
}
|
||||
|
||||
private class InvitationDocumentDelegate(
|
||||
private val onResult: (Result<String>) -> Unit,
|
||||
) : NSObject(), UIDocumentPickerDelegateProtocol {
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
override fun documentPicker(controller: UIDocumentPickerViewController, didPickDocumentsAtURLs: List<*>) {
|
||||
val url = didPickDocumentsAtURLs.firstOrNull() as? NSURL
|
||||
onResult(runCatching {
|
||||
requireNotNull(url) { "The selected invitation URL was invalid" }
|
||||
val path = url.path ?: error("The invitation path was invalid")
|
||||
val data = NSFileManager.defaultManager.contentsAtPath(path) ?: error("The invitation could not be opened")
|
||||
require(data.length.toLong() <= MaxInvitationBytes) { "The invitation is too large" }
|
||||
data.bytes?.readBytes(data.length.toInt())?.decodeToString() ?: error("The invitation is empty")
|
||||
})
|
||||
retainedInvitationDelegate = null
|
||||
}
|
||||
|
||||
override fun documentPickerWasCancelled(controller: UIDocumentPickerViewController) {
|
||||
retainedInvitationDelegate = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user