hotwired / turbo-android

Android framework for making Turbo native apps
MIT License
423 stars 51 forks source link

Add the missing onGeolocationPermissionsShowPrompt #193

Closed Nodalailama closed 2 years ago

Nodalailama commented 2 years ago

By adding this, developper can use the js native function to get the position of a user.

You will need to set the correct permission like __ and then request permissions ActivityCompat.requestPermissions

Voilà.

ghiculescu commented 2 years ago

I think this solution won't work out of the box, on newer Android versions you would need to request the location permission. And the UI for that is going to differ per-app. Here's roughly what we did to make it work without any custom UI.

// WebFragment.kt
class WebFragment : TurboWebFragment() {
    var permissionsCallback: ((Boolean, Boolean) -> Unit)? = null

    val locationPermissionRequest = fragment.registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        when {
            permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
                permissionsCallback?.let { it(true, true) }
            }
            permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
                permissionsCallback?.let { it(true, false) }
            }
            else -> {
                permissionsCallback?.let { it(false, false) }
            }
        }
        permissionsCallback = null
    }

    // ...

    override fun createWebChromeClient(): GeolocationPermissionsTurboWebChromeClient {
        return GeolocationPermissionsTurboWebChromeClient(this, session)
    }
}

class GeolocationPermissionsTurboWebChromeClient(
    private val fragment: WebFragment,
    s: TurboSession
) : TurboWebChromeClient(s) {
    override fun onGeolocationPermissionsShowPrompt(
        origin: String?,
        callback: GeolocationPermissions.Callback
    ) {
        if (ContextCompat.checkSelfPermission(
                App.sharedContext!!,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            callback.invoke(origin, true, true)
        } else {
            fragment.permissionsCallback = { allow: Boolean, retain: Boolean ->
                callback.invoke(origin, allow, retain)
            }

            fragment.locationPermissionRequest.launch(
                arrayOf(
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION
                )
            )
        }
    }
Nodalailama commented 2 years ago

Hi,

Thank you for sharing.

Nodalailama commented 2 years ago

@ghiculescu, may you would push a PR ?

ghiculescu commented 2 years ago

Let's see what @jayohms thinks.

I'm not sure this is generic enough that it should be included in the library for everyone.

Nodalailama commented 2 years ago

Okay. How are you extending it in your app without touching the library ?

ghiculescu commented 2 years ago

Using the above code - it just subclasses the library code.

jayohms commented 2 years ago

The TurboWebChromeClient is left open so it can be extended in any app-specific ways necessary. @ghiculescu's approach is the one you should take in your app. For example, in the Basecamp apps we override onJsAlert() and onJsConfirm() to display nicer Material dialogs:

WebViewChromeClient.kt

class WebViewChromeClient(session: TurboSession) : TurboWebChromeClient(session) {
    override fun onJsAlert(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
        val context = view?.context ?: return false

        MaterialAlertDialogBuilder(context)
            .setMessage(message)
            .setPositiveButton(context.getString(R.string.button_ok)) { dialog, _ ->
                dialog.cancel()
                result?.confirm()
            }
            .setCancelable(false)
            .create()
            .show()

        return true
    }

    override fun onJsConfirm(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
        val context = view?.context ?: return false

        MaterialAlertDialogBuilder(context)
            .setMessage(message)
            .setNegativeButton(context.getString(R.string.button_cancel)) { dialog, _ ->
                dialog.cancel()
                result?.cancel()
            }
            .setPositiveButton(context.getString(R.string.button_ok)) { dialog, _ ->
                dialog.cancel()
                result?.confirm()
            }
            .setCancelable(false)
            .create()
            .show()

        return true
    }

WebFragment.kt

class WebFragment : TurboWebFragment() {

    // ...

    override fun createWebChromeClient(): TurboWebChromeClient {
        return WebViewChromeClient(session)
    }
}

The Geolocation permission is too app-specific to be included in the library code and any WebChromeClient features not implemented in the library can be added in your own custom TurboWebChromeClient.

Further, this PR's approach is too simplistic and needs to prompt the user for the proper permission like @ghiculescu outlined.

Nodalailama commented 2 years ago

@ghiculescu, @jayohms, thank you very much.