Skip to content

Commit

Permalink
200.3.0 Release - merge v.next into main (#224)
Browse files Browse the repository at this point in the history
* enable the publication of artifacts which include the build number in the version.

* bump version

* add lambda to authenticator

* add util methods

* add lambda to DialogAuthenticator

* cherry pick to v.next (#103)

* use launchCustomTabs in OAuthUserSignInActivity

* fix issue with pressing cancel button

* Merge pull request #104 from Esri/sorenoid/update-auth-readme

use updated name of the auth dependency in README

* reinstate onWindowFocusChanged in OAuthUserSignInActivity

* use didLaunch logic in OAuthAuthenticator for lambda scenario as well

* rm context receivers

* remove ontopresumedactivitychanged

* Trigger onFacilityChanged along with the levelChanged when selectedLevelId is programmatically updated (#111)

* reorganize OAuthAuthenticator

* update readme

* Don't suggest singleInstance

* add doc to method

* eof newline

* move composable to activity scope

* mv viewmodel definition to class level

* mv Authenticator definition to setContent block

* pass vm as argument to auth app

* fix readme numbering

* add note about redirect uri

* update doc for new param

* simplify side effect

* Address pr comments

* rename Extensions file

* rework OAuthAuthenticator to move launcher

* update readme

* Deletes WebView's session if  `preferPrivateWebBrowserSession` is enabled  (#119)

* added geo composable map

* Update gradle

* Delete SceneComposable.kt

* Revert "Delete SceneComposable.kt"

This reverts commit 5f9b41e.

* upgrades to match the API's gradle versions so we can easily include the API as a composite build. Also clears some false warnings in gradle files

* renamed to module geo-compose

* fixes for displaying FloorFilter facilitySelector when Sites are not present in the map (#153)

* Create t9nmanifest.txt (#167)

* removed module and microapp (#208)

* Update gradle.properties (#211)

* Update build.gradle.kts (#212)

* updates client id and redirect Uri (#216)

* Take out build numbers for publishing (#219)

* update dependency section of readme for 200.3.0 (#220)

---------

Co-authored-by: Soren Roth <sor10874@esri.com>
Co-authored-by: Soren Roth <sroth@esri.com>
Co-authored-by: Hudson Miears <hud10837@esri.com>
Co-authored-by: hud10837 <hmiears@esri.com>
Co-authored-by: Erick Lopez Solis <erick_solis@esri.com>
Co-authored-by: Shubham Sharma <shubhamsharma@esri.com>
Co-authored-by: Rama Chintapalli <rchintapalli@esri.com>
Co-authored-by: Gunther Heppner <gheppner@esri.com>
  • Loading branch information
9 people authored Dec 6, 2023
1 parent 980dfa3 commit d923389
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 158 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ The *ArcGIS Maps SDK for Kotlin Toolkit* has a *Minimum SDK* version of *26*, me
The *ArcGIS Maps SDK for Kotlin Toolkit* is released with a "bill of materials" (`BOM`). The releasable BOM is versioned and represents a set of versions of the toolkit components which are compatible with one another. You may specify dependencies as follows

```
implementation(platform('com.esri:arcgis-maps-kotlin-toolkit-bom:200.2.0'))
implementation(platform('com.esri:arcgis-maps-kotlin-toolkit-bom:200.3.0'))
implementation('com.esri:arcgis-maps-kotlin-toolkit-authentication')
implementation('com.esri:arcgis-maps-kotlin-toolkit-compass')
implementation('com.esri:arcgis-maps-kotlin-toolkit-indoors')
```

The template and TemplateApp modules are for bootstrapping new modules.
Expand Down
5 changes: 0 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,9 @@

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
@Suppress("DSL_SCOPE_VIOLATION") // fixed in gradle 8.1
alias(libs.plugins.android.application) apply false
@Suppress("DSL_SCOPE_VIOLATION")
alias(libs.plugins.android.library) apply false
@Suppress("DSL_SCOPE_VIOLATION")
alias(libs.plugins.kotlin.android) apply false
@Suppress("DSL_SCOPE_VIOLATION")
alias(libs.plugins.gradle.secrets) apply false
@Suppress("DSL_SCOPE_VIOLATION")
alias(libs.plugins.kotlin.serialization) apply false
}
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ artifactoryUsername=""
artifactoryPassword=""
# these numbers will define the artifact version on artifactory
# and are overridden by the jenkins command line in the daily build
versionNumber=200.2.0
versionNumber=200.3.0
buildNumber=0000-snapshot
#set this flag to `true` to ignore the build number when publishing. This
# will publish an artifact with a build number like "..:200.2.0" as opposed to "...:200.2.0-3963
ignoreBuildNumber=true
# these versions define the dependency of the ArcGIS Maps SDK for Kotlin dependency
# and are generally not overridden at the command line unless a special build is requested.
sdkVersionNumber=200.2.0
sdkVersionNumber=200.3.0
sdkBuildNumber=


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]
androidGradlePlugin = "8.0.0"
androidGradlePlugin = "8.1.1"
androidxActivity = "1.5.1"
androidXBrowser = "1.5.0"
androidxCompose = "1.3.0"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
#Thu May 04 21:42:41 PDT 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@

<data
android:host="auth"
android:scheme="my-ags-app" />
android:scheme="kotlin-toolkit-authenticator-microapp" />
</intent-filter>
</activity>
</application>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class AuthenticationAppViewModel(application: Application) : AndroidViewModel(ap
arcGISUrl,
// This client ID is for demo purposes only. For use of the Authenticator in your own app,
// create your own client ID. For more info see: https://developers.arcgis.com/documentation/mapping-apis-and-services/security/tutorials/register-your-application/
"lgAdHkYZYlwwfAhC",
"my-ags-app://auth"
"aink3YEhnDNBBcJq",
"kotlin-toolkit-authenticator-microapp://auth"
)

private val _infoText: MutableStateFlow<String> = MutableStateFlow(startInfoText)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@

package com.arcgismaps.toolkit.authenticationapp

import android.app.Application
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand All @@ -44,36 +44,32 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.toolkit.authentication.AuthenticatorState
import com.arcgismaps.toolkit.authentication.DialogAuthenticator
import com.arcgismaps.toolkit.authenticationapp.ui.theme.AuthenticationAppTheme

class MainActivity : ComponentActivity() {
private val authenticationAppViewModel: AuthenticationAppViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Application context must be set for client certificate authentication.
ArcGISEnvironment.applicationContext = applicationContext
setContent {
AuthenticationAppTheme {
AuthenticationApp()
AuthenticationApp(authenticationAppViewModel)
DialogAuthenticator(authenticatorState = authenticationAppViewModel.authenticatorState)
}
}
}
}

@Composable
private fun AuthenticationApp() {
val application = LocalContext.current.applicationContext as Application
val authenticationAppViewModel = viewModel { AuthenticationAppViewModel(application) }
val authenticatorState: AuthenticatorState = authenticationAppViewModel.authenticatorState
private fun AuthenticationApp(authenticationAppViewModel: AuthenticationAppViewModel) {
Column {
val infoText = authenticationAppViewModel.infoText.collectAsState().value
val isLoading = authenticationAppViewModel.isLoading.collectAsState().value
Expand All @@ -87,7 +83,6 @@ private fun AuthenticationApp() {
)
InfoScreen(text = infoText, isLoading = isLoading)
}
DialogAuthenticator(authenticatorState = authenticatorState)
}

/**
Expand Down
2 changes: 2 additions & 0 deletions t9nmanifest.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
toolkit/authentication/src/main/res/values/strings.xml
toolkit/indoors/src/main/res/values/strings.xml
49 changes: 25 additions & 24 deletions toolkit/authentication/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ class MyAppViewModel(application: Application) : AndroidViewModel(application),
}
```

### Intercepting OAuth Sign-in Redirects
### Intercepting OAuth Sign-in

The `Authenticator` launches a Custom Tab when an OAuth challenge is issued. When the Custom Tab completes with a redirect url, it is received by the `OAuthUserSignInActivity` that is declared in your app's manifest via its intent filter.

If you want to intercept this redirect before allowing the sign-in to complete, you can do that with the following steps:
If you want to launch a Custom Tab from your own app's activity, these steps will allow you to do that:

1. Remove the `intent-filter` from the `OAuthUserSignInActivity` in your app's manifest and put it on the activity that you wish to receive the redirect intent:
1. Remove the `OAuthUserSignInActivity` in your app's manifest and put its intent filter on the activity that you wish to receive the redirect intent:

```xml
<activity
Expand All @@ -114,35 +114,36 @@ If you want to intercept this redirect before allowing the sign-in to complete,
android:scheme="my-ags-app" />
</intent-filter>
</activity>
```

<activity
android:name="com.arcgismaps.toolkit.authentication.OAuthUserSignInActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:exported="true"
android:launchMode="singleTop" >
</activity>
2. Call `launchCustomTabs` in the lambda `onPendingOAuthUserSignIn` of the `Authenticator`, passing in the pending `OAuthUserSignIn`:

```kotlin
DialogAuthenticator(authenticatorState = authenticatorState) { pendingSignIn ->
launchCustomTabs(pendingSignIn)
}
```

2. Handle the redirect in your app's activity:
3. Handle the redirect in your app activity's `onNewIntent` and `onResume` overrides:

Note: You can check if the `intent` was caused by an OAuth redirect because the `intent.data.toString()` will start with your OAuth configuration's redirect URI.

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Handle OAuth redirect intents by checking the intent.data
intent?.data?.let { uri ->
val uriString = uri.toString()
if (uriString.startsWith("my-ags-app")) {
// Do whatever business logic your app requires
val newIntent = Intent(this, OAuthUserSignInActivity::class.java).apply {
data = uri
}
startActivity(newIntent)
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// This gets called first when OAuth redirects back to the app with a successful or cancelled
// sign-in.
authenticationAppViewModel.authenticatorState.completeOAuthSignIn(intent)
}

override fun onResume() {
super.onResume()
// This gets called when the Custom Tab is closed using the close button or the phone's back button.
authenticationAppViewModel.authenticatorState.completeOAuthSignIn(intent)
}
```

Please note that the `OAuthUserSignInActivity` must have a `launchMode` of either `singleTop` or `singleInstance` for OAuth authentication to work.
Please note that your app's activity must have a `launchMode` of `singleTop` for OAuth authentication to work.

### Launch OAuth Prompts in Incognito Mode

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,24 @@ import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallenge
import com.arcgismaps.httpcore.authentication.OAuthUserConfiguration
import com.arcgismaps.httpcore.authentication.OAuthUserSignIn

/**
* Displays appropriate Authentication UI when issued a challenge. For example, if an [ArcGISAuthenticationChallenge]
* is issued and the [AuthenticatorState] has a corresponding [OAuthUserConfiguration],
* then a Custom Chrome Tab will be launched to complete the OAuth sign in.
*
* @param authenticatorState an [AuthenticatorState]. See [AuthenticatorState.Companion.Factory].
* @param onPendingOAuthUserSignIn if not null, this will be called when an OAuth challenge is pending
* and the browser should be launched. Use this if you wish to handle OAuth challenges from your own
* activity rather than using the [OAuthUserSignInActivity].
* @since 200.2.0
*/
@Composable
public fun Authenticator(
authenticatorState: AuthenticatorState,
modifier: Modifier = Modifier.fillMaxSize()
modifier: Modifier = Modifier.fillMaxSize(),
onPendingOAuthUserSignIn: ((OAuthUserSignIn) -> Unit)? = null
) {
// If the back button is pressed, this ensures that any prompts get dismissed.
BackHandler {
Expand All @@ -58,7 +63,7 @@ public fun Authenticator(
authenticatorState.pendingOAuthUserSignIn.collectAsStateWithLifecycle().value

pendingOAuthUserSignIn?.let {
OAuthAuthenticator(it, authenticatorState)
OAuthAuthenticator(it, authenticatorState, onPendingOAuthUserSignIn)
}

val pendingServerTrustChallenge =
Expand Down Expand Up @@ -104,18 +109,22 @@ private fun Context.findActivity(): Activity {
/**
* Displays the [Authenticator] in an [AlertDialog]. See the Authenticator component for more details.
*
* @param authenticatorState an [AuthenticatorState]. See [AuthenticatorState.Companion.Factory].
* @param onPendingOAuthUserSignIn if not null, this will be called when an OAuth challenge is pending
* and the browser should be launched. Use this if you wish to handle OAuth challenges from your own
* activity rather than using the [OAuthUserSignInActivity].
* @since 200.2.0
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
public fun DialogAuthenticator(authenticatorState: AuthenticatorState) {
public fun DialogAuthenticator(authenticatorState: AuthenticatorState, onPendingOAuthUserSignIn: ((OAuthUserSignIn) -> Unit)? = null) {
val showDialog = authenticatorState.isDisplayed.collectAsStateWithLifecycle(initialValue = false).value
if (showDialog) {
AlertDialog(
onDismissRequest = { authenticatorState.dismissAll() },
modifier = Modifier.clip(MaterialTheme.shapes.extraLarge),
) {
Authenticator(authenticatorState = authenticatorState, modifier = Modifier.fillMaxWidth())
Authenticator(authenticatorState = authenticatorState, modifier = Modifier.fillMaxWidth(), onPendingOAuthUserSignIn)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.arcgismaps.toolkit.authentication

import android.content.Intent
import android.security.KeyChainAliasCallback
import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallenge
Expand Down Expand Up @@ -391,6 +392,21 @@ private suspend fun OAuthUserConfiguration.handleOAuthChallenge(
onPendingSignIn(oAuthUserSignIn)
}

/**
* Completes the current [AuthenticatorState.pendingOAuthUserSignIn] with data from the provided [intent].
*
* The [intent.data] should contain a string representing the redirect URI that came from a browser
* where the OAuth sign-in was performed. If the data is null, the sign-in will be cancelled.
*
* @since 200.3.0
*/
public fun AuthenticatorState.completeOAuthSignIn(intent: Intent?) {
intent?.data?.let {
val uriString = it.toString()
pendingOAuthUserSignIn.value?.complete(uriString)
} ?: pendingOAuthUserSignIn.value?.cancel()
}

/**
* Represents a username and password pair.
*
Expand Down
Loading

0 comments on commit d923389

Please sign in to comment.