diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 0599b91..3805133 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6dfaa05..126ca8e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,15 +18,42 @@
android:name=".QuilttConnectorActivity"
android:launchMode="singleTop"
android:exported="true">
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- println("Event: $eventType")
- println("Metadata: $metadata")
- },
- onExit = { eventType, metadata ->
- println("Event: $eventType")
- println("Metadata: $metadata")
- },
- onExitSuccess = { metadata ->
- println("Exit success!")
- println("Metadata: $metadata")
- Toast.makeText(context, metadata.connectionId, Toast.LENGTH_LONG).show()
- if (context is Activity) {
- context.finish()
- }
- },
- onExitAbort = { metadata ->
- println("Exit abort!")
- println("Metadata: $metadata")
- if (context is Activity) {
- context.finish()
- }
- },
- onExitError = { metadata ->
- println("Exit error!")
- println("Metadata: $metadata")
- if (context is Activity) {
- context.finish()
- }
- })
- AndroidView(factory = { connectorWebView } )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun QuilttConnectorPreview() {
- QuilttConnectorContent(
- config = QuilttConnectorConnectConfiguration(
- connectorId = "",
- oauthRedirectUrl = ""
- )
- )
-}
\ No newline at end of file
diff --git a/app_jetpack_compose/src/main/res/values/strings.xml b/app_jetpack_compose/src/main/res/values/strings.xml
deleted file mode 100644
index 592f1df..0000000
--- a/app_jetpack_compose/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
- App Jetpack Compose
- QuilttConnectorActivity
-
\ No newline at end of file
diff --git a/app_jetpack_compose/.gitignore b/app_quiltthub_webview_example/.gitignore
similarity index 100%
rename from app_jetpack_compose/.gitignore
rename to app_quiltthub_webview_example/.gitignore
diff --git a/app_jetpack_compose/build.gradle.kts b/app_quiltthub_webview_example/build.gradle.kts
similarity index 78%
rename from app_jetpack_compose/build.gradle.kts
rename to app_quiltthub_webview_example/build.gradle.kts
index f385042..49a4db2 100644
--- a/app_jetpack_compose/build.gradle.kts
+++ b/app_quiltthub_webview_example/build.gradle.kts
@@ -4,11 +4,11 @@ plugins {
}
android {
- namespace = "app.quiltt.app_jetpack_compose"
+ namespace = "app.quiltt.app_quiltthub_webview_example"
compileSdk = 34
defaultConfig {
- applicationId = "app.quiltt.app_jetpack_compose"
+ applicationId = "app.quiltt.app_quiltthub_webview_example"
minSdk = 26
targetSdk = 34
versionCode = 1
@@ -18,6 +18,15 @@ android {
vectorDrawables {
useSupportLibrary = true
}
+
+ buildFeatures {
+ buildConfig = true
+ }
+
+ val ingressConnectorId = System.getenv("MOBILE_INGRESS_CONNECTOR_ID")
+ val addConnectorId = System.getenv("MOBILE_ADD_CONNECTOR_ID")
+ buildConfigField("String", "INGRESS_CONNECTOR_ID", "\"$ingressConnectorId\"")
+ buildConfigField("String", "ADD_CONNECTOR_ID", "\"$addConnectorId\"")
}
buildTypes {
@@ -70,6 +79,8 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("androidx.core:core-splashscreen:1.0.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
+ implementation("androidx.browser:browser:1.4.0")
implementation(project(":connector"))
// Enable below to use the published version
// implementation("app.quiltt:connector:")
diff --git a/app_jetpack_compose/proguard-rules.pro b/app_quiltthub_webview_example/proguard-rules.pro
similarity index 100%
rename from app_jetpack_compose/proguard-rules.pro
rename to app_quiltthub_webview_example/proguard-rules.pro
diff --git a/app_jetpack_compose/src/androidTest/java/app/quiltt/app_jetpack_compose/ExampleInstrumentedTest.kt b/app_quiltthub_webview_example/src/androidTest/java/app/quiltt/app_quiltthub_webview_example/ExampleInstrumentedTest.kt
similarity index 92%
rename from app_jetpack_compose/src/androidTest/java/app/quiltt/app_jetpack_compose/ExampleInstrumentedTest.kt
rename to app_quiltthub_webview_example/src/androidTest/java/app/quiltt/app_quiltthub_webview_example/ExampleInstrumentedTest.kt
index fbca9f9..30c48ad 100644
--- a/app_jetpack_compose/src/androidTest/java/app/quiltt/app_jetpack_compose/ExampleInstrumentedTest.kt
+++ b/app_quiltthub_webview_example/src/androidTest/java/app/quiltt/app_quiltthub_webview_example/ExampleInstrumentedTest.kt
@@ -1,4 +1,4 @@
-package app.quiltt.app_jetpack_compose
+package app.quiltt.app_quiltthub_webview_example
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/app_jetpack_compose/src/main/AndroidManifest.xml b/app_quiltthub_webview_example/src/main/AndroidManifest.xml
similarity index 68%
rename from app_jetpack_compose/src/main/AndroidManifest.xml
rename to app_quiltthub_webview_example/src/main/AndroidManifest.xml
index bf862b2..2c49c99 100644
--- a/app_jetpack_compose/src/main/AndroidManifest.xml
+++ b/app_quiltthub_webview_example/src/main/AndroidManifest.xml
@@ -1,45 +1,42 @@
-
+
+
-
-
-
-
-
+
-
-
-
-
\ No newline at end of file
diff --git a/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/AppConfig.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/AppConfig.kt
new file mode 100644
index 0000000..1959733
--- /dev/null
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/AppConfig.kt
@@ -0,0 +1,7 @@
+package app.quiltt.app_quiltthub_webview_example
+
+object AppConfig {
+ val ingressConnectorId = BuildConfig.INGRESS_CONNECTOR_ID
+ val addConnectorId = BuildConfig.ADD_CONNECTOR_ID
+ val oauthRedirectUrl = "https://quiltt.app/mobile/hub/reconnect"
+}
\ No newline at end of file
diff --git a/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/MainActivity.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/MainActivity.kt
new file mode 100644
index 0000000..5357141
--- /dev/null
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/MainActivity.kt
@@ -0,0 +1,103 @@
+package app.quiltt.app_quiltthub_webview_example
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+
+// Splash screen
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import androidx.activity.viewModels
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.ViewModelProvider
+import app.quiltt.connector.PingResponse
+import app.quiltt.connector.QuilttAuthApi
+import app.quiltt.connector.QuilttConnector
+import app.quiltt.connector.QuilttConnectorConnectConfiguration
+import kotlinx.coroutines.Dispatchers
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val token = SharedPreferencesHelper(context = this).getData("token")
+ val viewModel: MainViewModel by viewModels { MainViewModelFactory(token) }
+
+ installSplashScreen().apply {
+ setKeepOnScreenCondition {
+ viewModel.loading.value
+ }
+ }
+
+ setContent {
+ val isValidToken by viewModel.isValidToken.collectAsState()
+ if (isValidToken) {
+ val intent = Intent(this@MainActivity, QuilttHubActivity::class.java)
+ startActivity(intent)
+ } else {
+ IngressConnector()
+ }
+ }
+ }
+}
+
+class MainViewModelFactory(private val token: String?) : ViewModelProvider.NewInstanceFactory() {
+ override fun create(modelClass: Class): T = MainViewModel(token) as T
+}
+
+class MainViewModel(private val token:String?) : ViewModel() {
+ private val _loading = MutableStateFlow(true)
+ private val _isValidToken = MutableStateFlow(false)
+ val loading = _loading.asStateFlow()
+ val isValidToken = _isValidToken.asStateFlow()
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {// run background task here
+ if (token != null) {
+ val result = QuilttAuthApi(clientId = null).ping(token = token)
+ if (result is PingResponse.SessionResponse) {
+ _isValidToken.value = true
+ _loading.value = false
+ } else {
+ _isValidToken.value = false
+ _loading.value = false
+ }
+ } else {
+ _isValidToken.value = false
+ _loading.value = false
+ }
+ }
+ }
+}
+
+@Composable
+fun IngressConnector() {
+ val context = LocalContext.current
+ val quilttConnector = QuilttConnector(context)
+ val config = QuilttConnectorConnectConfiguration(connectorId = AppConfig.ingressConnectorId)
+ val connectorWebView = quilttConnector.connect(config = config, onExitSuccess = { metadata ->
+ val token: String? = metadata.token
+ if (token != null) {
+ SharedPreferencesHelper(context).saveData("token", token)
+ val intent = Intent(context, QuilttHubActivity::class.java)
+ context.startActivity(intent)
+ (context as MainActivity).finish()
+ }
+ })
+ AndroidView(factory = { connectorWebView } )
+}
+
+@Preview(showBackground = true)
+@Composable
+fun QuilttHubPreview() {
+ IngressConnector()
+}
\ No newline at end of file
diff --git a/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/QuilttConnectorActivity.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/QuilttConnectorActivity.kt
new file mode 100644
index 0000000..b9d3289
--- /dev/null
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/QuilttConnectorActivity.kt
@@ -0,0 +1,106 @@
+package app.quiltt.app_quiltthub_webview_example
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.viewinterop.AndroidView
+import app.quiltt.connector.QuilttConnector
+import app.quiltt.connector.QuilttConnectorConnectConfiguration
+import app.quiltt.connector.QuilttConnectorReconnectConfiguration
+
+class QuilttConnectorActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val connectionId = intent.getStringExtra("connectionId")
+ val connectorId = AppConfig.addConnectorId
+ val oauthRedirectUrl = AppConfig.oauthRedirectUrl
+ val token = SharedPreferencesHelper(context = this).getData("token")
+ if (connectionId != null) {
+ val config = QuilttConnectorReconnectConfiguration(
+ connectorId = connectorId,
+ oauthRedirectUrl = oauthRedirectUrl,
+ connectionId = connectionId
+ )
+ setContent {
+ QuilttReconnectContent(config = config, token = token!!)
+ }
+ } else {
+ val config = QuilttConnectorConnectConfiguration(
+ connectorId = connectorId,
+ oauthRedirectUrl = oauthRedirectUrl)
+ setContent {
+ QuilttConnectContent(config = config, token = token!!)
+ }
+ }
+ }
+}
+
+@Composable
+fun QuilttConnectContent(config: QuilttConnectorConnectConfiguration, token: String) {
+ val context = LocalContext.current
+ val quilttConnector = QuilttConnector(context)
+ quilttConnector.authenticate(token)
+ val connectorWebView = quilttConnector.connect(
+ config = config,
+ onExitSuccess = { metadata ->
+ if (context is Activity) {
+ context.finish()
+ }
+ },
+ onExitAbort = { metadata ->
+ // TODO: Handle abort
+ if (context is Activity) {
+ context.finish()
+ }
+ },
+ onExitError = { metadata ->
+ // TODO: Handle error
+ if (context is Activity) {
+ context.finish()
+ }
+ })
+ AndroidView(factory = { connectorWebView } )
+}
+
+@Composable
+fun QuilttReconnectContent(config: QuilttConnectorReconnectConfiguration, token: String) {
+ val context = LocalContext.current
+ val quilttConnector = QuilttConnector(context)
+ quilttConnector.authenticate(token)
+ val connectorWebView = quilttConnector.reconnect(
+ config = config,
+ onExitSuccess = { metadata ->
+ if (context is Activity) {
+ context.finish()
+ }
+ },
+ onExitAbort = { metadata ->
+ // TODO: Handle abort
+ if (context is Activity) {
+ context.finish()
+ }
+ },
+ onExitError = { metadata ->
+ // TODO: Handle error
+ if (context is Activity) {
+ context.finish()
+ }
+ })
+ AndroidView(factory = { connectorWebView } )
+}
+
+@Preview(showBackground = true)
+@Composable
+fun QuilttConnectorPreview() {
+ QuilttConnectContent(
+ config = QuilttConnectorConnectConfiguration(
+ connectorId = "",
+ oauthRedirectUrl = ""
+ ),
+ token = ""
+ )
+}
\ No newline at end of file
diff --git a/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/QuilttHubActivity.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/QuilttHubActivity.kt
new file mode 100644
index 0000000..bef685e
--- /dev/null
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/QuilttHubActivity.kt
@@ -0,0 +1,168 @@
+package app.quiltt.app_quiltthub_webview_example
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.ViewGroup
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ExitToApp
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.viewinterop.AndroidView
+import app.quiltt.connector.QuilttAuthApi
+import android.app.AlertDialog
+import android.net.Uri
+import android.webkit.WebResourceRequest
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.res.painterResource
+
+class QuilttHubActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ QuilttHubContent()
+ }
+ }
+
+ override fun onBackPressed() {
+ showLogoutConfirmationDialog()
+ }
+ fun showLogoutConfirmationDialog() {
+ AlertDialog.Builder(this)
+ .setTitle("Confirm Logout")
+ .setMessage("Are you sure you want to logout?")
+ .setPositiveButton("Yes") { _, _ ->
+ logout()
+// startActivity((Intent(this, MainActivity::class.java)))
+ }
+ .setNegativeButton("No", null)
+ .show()
+ }
+
+ fun launchWithCustomTab(url: String) {
+ val builder = CustomTabsIntent.Builder()
+ val customTabsIntent = builder.build()
+ customTabsIntent.launchUrl(this, Uri.parse(url))
+ }
+ private fun logout() {
+ val token = SharedPreferencesHelper(context = this).getData("token")
+ val apiClient = QuilttAuthApi(clientId = null)
+ CoroutineScope(Dispatchers.IO).launch {
+ apiClient.revoke(token = token!!)
+ }
+ this.finish()
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun QuilttHubContent() {
+ val context = LocalContext.current
+ val activity = context as QuilttHubActivity
+ val token = SharedPreferencesHelper(context = context).getData("token")
+ val url = "https://www.quiltthub.com/login?mode=webview&token=$token"
+ Column {
+ TopAppBar(
+ title = {
+ Image(
+ painter = painterResource(id = R.drawable.quiltt_app_icon),
+ contentDescription = "Quiltt Hub Logo",
+ modifier = Modifier.size(40.dp))
+ },
+ actions = {
+ Button(onClick = {
+ val intent = Intent(context, QuilttConnectorActivity::class.java)
+ context.startActivity(intent)
+ },
+ shape = RoundedCornerShape(25),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = colorResource(id = R.color.purple_500),
+ contentColor = Color.White))
+ {
+ Text("Add Account")
+ }
+ IconButton(onClick = {
+ activity.showLogoutConfirmationDialog()
+ }) {
+ Icon(Icons.Filled.ExitToApp, contentDescription = "Logout")
+ }
+ }
+ )
+ WebViewComposable(url = url)
+ }
+}
+
+@Composable
+fun WebViewComposable(url: String) {
+ AndroidView(factory = { context ->
+ WebView(context).apply {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ settings.javaScriptEnabled = true
+ settings.domStorageEnabled = true
+ webViewClient = object : WebViewClient() {
+ val activity = context as QuilttHubActivity
+ override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
+ val url = request?.url?.toString()
+ if (url != null && url.startsWith("https://www.quiltthub.com/mobile/hub/reconnect")) {
+ val urlComponents = Uri.parse(url)
+ val connectionId = urlComponents.getQueryParameter("connectionId")
+ val intent = Intent(context, QuilttConnectorActivity::class.java)
+ intent.putExtra("connectionId", connectionId)
+ activity.startActivity(intent)
+ return true
+ }
+ if (url != null && url.startsWith("https://www.quiltthub.com")) {
+ return false
+ }
+ // TODO: Sometimes connector is publishing Options?
+ if (url != null && url.startsWith("quilttconnector://")) {
+ return true
+ }
+ // TODO: handle token expires while the user is using the app
+
+ // Handle quiltt.io links, mailto links and other social media links
+ if (url != null) {
+ activity.launchWithCustomTab(url)
+ return true
+ }
+ // Return false to let the WebView handle the URL
+ return false
+ }
+ }
+ loadUrl(url)
+ }
+ }, update = { webView ->
+ webView.loadUrl(url)
+ })
+}
+
+@Preview(showBackground = true)
+@Composable
+fun QuilttHubActivityPreview() {
+ QuilttHubContent()
+}
\ No newline at end of file
diff --git a/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/SharedPreferencesHelper.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/SharedPreferencesHelper.kt
new file mode 100644
index 0000000..ee55fff
--- /dev/null
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/SharedPreferencesHelper.kt
@@ -0,0 +1,19 @@
+package app.quiltt.app_quiltthub_webview_example
+
+import android.content.Context
+import android.content.SharedPreferences
+
+class SharedPreferencesHelper(context: Context) {
+ private val sharedPreferences: SharedPreferences =
+ context.getSharedPreferences("MyAppPreferences", Context.MODE_PRIVATE)
+
+ fun saveData(key: String, value: String) {
+ val editor = sharedPreferences.edit()
+ editor.putString(key, value)
+ editor.apply()
+ }
+
+ fun getData(key: String): String? {
+ return sharedPreferences.getString(key, null)
+ }
+}
\ No newline at end of file
diff --git a/app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Color.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Color.kt
similarity index 80%
rename from app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Color.kt
rename to app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Color.kt
index d336c01..ea54166 100644
--- a/app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Color.kt
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Color.kt
@@ -1,4 +1,4 @@
-package app.quiltt.app_jetpack_compose.ui.theme
+package app.quiltt.app_quiltthub_webview_example.ui.theme
import androidx.compose.ui.graphics.Color
diff --git a/app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Theme.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Theme.kt
similarity index 97%
rename from app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Theme.kt
rename to app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Theme.kt
index 6a81000..7fda95f 100644
--- a/app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Theme.kt
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Theme.kt
@@ -1,4 +1,4 @@
-package app.quiltt.app_jetpack_compose.ui.theme
+package app.quiltt.app_quiltthub_webview_example.ui.theme
import android.app.Activity
import android.os.Build
diff --git a/app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Type.kt b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Type.kt
similarity index 94%
rename from app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Type.kt
rename to app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Type.kt
index a99d074..9969be1 100644
--- a/app_jetpack_compose/src/main/java/app/quiltt/app_jetpack_compose/ui/theme/Type.kt
+++ b/app_quiltthub_webview_example/src/main/java/app/quiltt/app_quiltthub_webview_example/ui/theme/Type.kt
@@ -1,4 +1,4 @@
-package app.quiltt.app_jetpack_compose.ui.theme
+package app.quiltt.app_quiltthub_webview_example.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
diff --git a/app_jetpack_compose/src/main/res/drawable/ic_launcher_background.xml b/app_quiltthub_webview_example/src/main/res/drawable/ic_launcher_background.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/drawable/ic_launcher_background.xml
rename to app_quiltthub_webview_example/src/main/res/drawable/ic_launcher_background.xml
diff --git a/app_jetpack_compose/src/main/res/drawable/ic_launcher_foreground.xml b/app_quiltthub_webview_example/src/main/res/drawable/ic_launcher_foreground.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/drawable/ic_launcher_foreground.xml
rename to app_quiltthub_webview_example/src/main/res/drawable/ic_launcher_foreground.xml
diff --git a/app_jetpack_compose/src/main/res/drawable/quiltt.xml b/app_quiltthub_webview_example/src/main/res/drawable/quiltt.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/drawable/quiltt.xml
rename to app_quiltthub_webview_example/src/main/res/drawable/quiltt.xml
diff --git a/app_quiltthub_webview_example/src/main/res/drawable/quiltt_app_icon.xml b/app_quiltthub_webview_example/src/main/res/drawable/quiltt_app_icon.xml
new file mode 100644
index 0000000..9d9568c
--- /dev/null
+++ b/app_quiltthub_webview_example/src/main/res/drawable/quiltt_app_icon.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app_jetpack_compose/src/main/res/mipmap-anydpi/ic_launcher.xml b/app_quiltthub_webview_example/src/main/res/mipmap-anydpi/ic_launcher.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-anydpi/ic_launcher.xml
rename to app_quiltthub_webview_example/src/main/res/mipmap-anydpi/ic_launcher.xml
diff --git a/app_jetpack_compose/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app_quiltthub_webview_example/src/main/res/mipmap-anydpi/ic_launcher_round.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-anydpi/ic_launcher_round.xml
rename to app_quiltthub_webview_example/src/main/res/mipmap-anydpi/ic_launcher_round.xml
diff --git a/app_jetpack_compose/src/main/res/mipmap-hdpi/ic_launcher.webp b/app_quiltthub_webview_example/src/main/res/mipmap-hdpi/ic_launcher.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-hdpi/ic_launcher.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-hdpi/ic_launcher.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app_quiltthub_webview_example/src/main/res/mipmap-hdpi/ic_launcher_round.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-hdpi/ic_launcher_round.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-hdpi/ic_launcher_round.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-mdpi/ic_launcher.webp b/app_quiltthub_webview_example/src/main/res/mipmap-mdpi/ic_launcher.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-mdpi/ic_launcher.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-mdpi/ic_launcher.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app_quiltthub_webview_example/src/main/res/mipmap-mdpi/ic_launcher_round.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-mdpi/ic_launcher_round.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-mdpi/ic_launcher_round.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app_quiltthub_webview_example/src/main/res/mipmap-xhdpi/ic_launcher.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-xhdpi/ic_launcher.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-xhdpi/ic_launcher.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app_quiltthub_webview_example/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app_quiltthub_webview_example/src/main/res/mipmap-xxhdpi/ic_launcher.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-xxhdpi/ic_launcher.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-xxhdpi/ic_launcher.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app_quiltthub_webview_example/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app_quiltthub_webview_example/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
diff --git a/app_jetpack_compose/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app_quiltthub_webview_example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
similarity index 100%
rename from app_jetpack_compose/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
rename to app_quiltthub_webview_example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
diff --git a/app_jetpack_compose/src/main/res/values/colors.xml b/app_quiltthub_webview_example/src/main/res/values/colors.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/values/colors.xml
rename to app_quiltthub_webview_example/src/main/res/values/colors.xml
diff --git a/app_jetpack_compose/src/main/res/values/splash.xml b/app_quiltthub_webview_example/src/main/res/values/splash.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/values/splash.xml
rename to app_quiltthub_webview_example/src/main/res/values/splash.xml
diff --git a/app_quiltthub_webview_example/src/main/res/values/strings.xml b/app_quiltthub_webview_example/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8288d6e
--- /dev/null
+++ b/app_quiltthub_webview_example/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+ Quiltt Hub
+ QuilttConnectorActivity
+ QuilttHubActivity
+
\ No newline at end of file
diff --git a/app_jetpack_compose/src/main/res/values/themes.xml b/app_quiltthub_webview_example/src/main/res/values/themes.xml
similarity index 100%
rename from app_jetpack_compose/src/main/res/values/themes.xml
rename to app_quiltthub_webview_example/src/main/res/values/themes.xml
diff --git a/app_jetpack_compose/src/test/java/app/quiltt/app_jetpack_compose/ExampleUnitTest.kt b/app_quiltthub_webview_example/src/test/java/app/quiltt/app_quiltthub_webview_example/ExampleUnitTest.kt
similarity index 86%
rename from app_jetpack_compose/src/test/java/app/quiltt/app_jetpack_compose/ExampleUnitTest.kt
rename to app_quiltthub_webview_example/src/test/java/app/quiltt/app_quiltthub_webview_example/ExampleUnitTest.kt
index cfa3755..29bcdcb 100644
--- a/app_jetpack_compose/src/test/java/app/quiltt/app_jetpack_compose/ExampleUnitTest.kt
+++ b/app_quiltthub_webview_example/src/test/java/app/quiltt/app_quiltthub_webview_example/ExampleUnitTest.kt
@@ -1,4 +1,4 @@
-package app.quiltt.app_jetpack_compose
+package app.quiltt.app_quiltthub_webview_example
import org.junit.Test
diff --git a/connector/src/main/AndroidManifest.xml b/connector/src/main/AndroidManifest.xml
index a5918e6..74b7379 100644
--- a/connector/src/main/AndroidManifest.xml
+++ b/connector/src/main/AndroidManifest.xml
@@ -1,4 +1,3 @@
-
\ No newline at end of file
diff --git a/connector/src/main/java/app/quiltt/connector/QuilttAuthApi.kt b/connector/src/main/java/app/quiltt/connector/QuilttAuthApi.kt
new file mode 100644
index 0000000..cd05d25
--- /dev/null
+++ b/connector/src/main/java/app/quiltt/connector/QuilttAuthApi.kt
@@ -0,0 +1,162 @@
+package app.quiltt.connector
+
+import org.json.JSONObject
+import java.io.InputStream
+import java.net.HttpURLConnection
+import java.net.URL
+
+sealed class UsernamePayload {
+ data class Email(val email: String) : UsernamePayload()
+ data class Phone(val phone: String) : UsernamePayload()
+}
+
+data class PasscodePayload(val usernamePayload: UsernamePayload, val passcode: String)
+
+data class SessionData(val token: String)
+data class UnauthorizedData(val message: String, val instruction: String)
+data class UnprocessableData(val attribute: Map)
+
+sealed class PingResponse {
+ data class SessionResponse(val status: Int, val data: SessionData) : PingResponse()
+ data class UnprocessableResponse(val status: Int, val data: UnprocessableData) : PingResponse()
+}
+
+sealed class IdentifyResponse {
+ data class SessionResponse(val status: Int, val data: SessionData) : IdentifyResponse()
+ data class AcceptedResponse(val status: Int) : IdentifyResponse()
+ data class UnprocessableResponse(val status: Int, val data: UnprocessableData) : IdentifyResponse()
+}
+
+sealed class AuthenticateResponse {
+ data class SessionResponse(val status: Int, val data: SessionData) : AuthenticateResponse()
+ data class UnauthorizedResponse(val status: Int, val data: UnauthorizedData) : AuthenticateResponse()
+ data class UnprocessableResponse(val status: Int, val data: UnprocessableData) : AuthenticateResponse()
+}
+
+sealed class RevokeResponse {
+ data class NoContentResponse(val status: Int) : RevokeResponse()
+ data class UnauthorizedResponse(val status: Int, val data: UnauthorizedData) : RevokeResponse()
+}
+
+class QuilttAuthApi(private val clientId: String?) {
+ private val endpointAuth = "https://auth.quiltt.app/v1/users/session"
+
+ /**
+ * Response Statuses:
+ * - 200: OK -> Session is Valid
+ * - 401: Unauthorized -> Session is Invalid
+ */
+ fun ping(token: String): PingResponse {
+ val url = URL(endpointAuth)
+ val connection = url.openConnection() as HttpURLConnection
+ connection.requestMethod = "GET"
+ connection.setRequestProperty("Content-Type", "application/json; utf-8")
+ connection.setRequestProperty("Authorization", "Bearer $token")
+
+ val statusCode = connection.responseCode
+
+ if (statusCode == 200) {
+ return PingResponse.SessionResponse(statusCode, SessionData(token))
+ }
+ val inputStream = connection.errorStream
+ val errorData = errorMap(inputStream)
+ return PingResponse.UnprocessableResponse(statusCode, errorData)
+ }
+
+ /**
+ * Response Statuses:
+ * - 201: Created -> Profile Created, New Session Returned
+ * - 202: Accepted -> Profile Found, MFA Code Sent for `authenticate`
+ * - 422: Unprocessable Entity -> Invalid Payload
+ */
+ fun identify(payload: UsernamePayload): IdentifyResponse {
+ val url = URL(endpointAuth)
+ val connection = url.openConnection() as HttpURLConnection
+ connection.requestMethod = "POST"
+ connection.setRequestProperty("Content-Type", "application/json; utf-8")
+ connection.doOutput = true
+
+ val jsonPayload = JSONObject()
+ jsonPayload.put("clientId", clientId)
+ when (payload) {
+ is UsernamePayload.Email -> jsonPayload.put("email", payload.email)
+ is UsernamePayload.Phone -> jsonPayload.put("phone", payload.phone)
+ }
+
+ if (connection.responseCode == 201) {
+ val inputStream = connection.inputStream
+ val jsonObject = JSONObject(inputStream.bufferedReader().use { it.readText() })
+ return IdentifyResponse.SessionResponse(connection.responseCode, SessionData(jsonObject.getString("token")))
+ }
+ if (connection.responseCode == 202) {
+ return IdentifyResponse.AcceptedResponse(connection.responseCode)
+ }
+ val inputStream = connection.errorStream
+ val errorData = errorMap(inputStream)
+ return IdentifyResponse.UnprocessableResponse(connection.responseCode, errorData)
+ }
+
+ /**
+ * Response Statuses:
+ * - 201: Created -> MFA Validated, New Session Returned
+ * - 401: Unauthorized -> MFA Invalid
+ * - 422: Unprocessable Entity -> Invalid Payload
+ */
+ fun authenticate(payload: PasscodePayload): AuthenticateResponse {
+ val url = URL(endpointAuth)
+ val connection = url.openConnection() as HttpURLConnection
+ connection.requestMethod = "PUT"
+ connection.setRequestProperty("Content-Type", "application/json; utf-8")
+ connection.doOutput = true
+
+ val jsonPayload = JSONObject()
+ jsonPayload.put("clientId", clientId)
+ jsonPayload.put("payload", payload)
+
+ if (connection.responseCode == 201) {
+ val inputStream = connection.inputStream
+ val jsonObject = JSONObject(inputStream.bufferedReader().use { it.readText() })
+ return AuthenticateResponse.SessionResponse(connection.responseCode, SessionData(jsonObject.getString("token")))
+ }
+ if (connection.responseCode == 401) {
+ val inputStream = connection.errorStream
+ val errorData = errorMap(inputStream) as UnauthorizedData
+ return AuthenticateResponse.UnauthorizedResponse(connection.responseCode, errorData)
+ }
+ val inputStream = connection.errorStream
+ val errorData = errorMap(inputStream)
+ return AuthenticateResponse.UnprocessableResponse(connection.responseCode, errorData)
+ }
+
+ /**
+ * Response Statuses:
+ * - 204: No Content -> Session Revoked
+ * - 401: Unauthorized -> Session Not Found
+ */
+ fun revoke(token: String): RevokeResponse {
+ val url = URL(endpointAuth)
+ val connection = url.openConnection() as HttpURLConnection
+ connection.requestMethod = "DELETE"
+ connection.setRequestProperty("Authorization", "Bearer $token")
+ connection.responseCode
+
+ if (connection.responseCode == 204) {
+ return RevokeResponse.NoContentResponse(connection.responseCode)
+ }
+ val inputStream = connection.errorStream
+ val errorData = errorMap(inputStream) as UnauthorizedData
+ return RevokeResponse.UnauthorizedResponse(connection.responseCode, errorData)
+ }
+
+ private fun errorMap(inputStream: InputStream): UnprocessableData {
+ val jsonObject = JSONObject(inputStream.bufferedReader().use { it.readText() })
+ val errorMap = mutableMapOf()
+ val keys = jsonObject.keys()
+ while (keys.hasNext()) {
+ val key = keys.next()
+ val value = jsonObject.getString(key)
+ errorMap[key] = value
+ }
+ return UnprocessableData(errorMap)
+ }
+}
diff --git a/connector/src/main/java/app/quiltt/connector/QuilttConnectorConfiguration.kt b/connector/src/main/java/app/quiltt/connector/QuilttConnectorConfiguration.kt
index b2b81d2..135e2a9 100644
--- a/connector/src/main/java/app/quiltt/connector/QuilttConnectorConfiguration.kt
+++ b/connector/src/main/java/app/quiltt/connector/QuilttConnectorConfiguration.kt
@@ -2,13 +2,13 @@ package app.quiltt.connector
interface QuilttConnectorConfiguration {
val connectorId: String
- val oauthRedirectUrl: String
+ val oauthRedirectUrl: String?
val connectionId: String?
}
data class QuilttConnectorConnectConfiguration(
override val connectorId: String,
- override val oauthRedirectUrl: String,
+ override val oauthRedirectUrl: String? = null
) : QuilttConnectorConfiguration {
override val connectionId: String? = null // always null for connect, cannot be set
}
diff --git a/connector/src/main/java/app/quiltt/connector/QuilttConnectorEvent.kt b/connector/src/main/java/app/quiltt/connector/QuilttConnectorEvent.kt
index 2d44a98..5da45e7 100644
--- a/connector/src/main/java/app/quiltt/connector/QuilttConnectorEvent.kt
+++ b/connector/src/main/java/app/quiltt/connector/QuilttConnectorEvent.kt
@@ -3,7 +3,8 @@ package app.quiltt.connector
data class ConnectorSDKCallbackMetadata(
val connectorId: String,
val profileId: String?,
- val connectionId: String?
+ val connectionId: String?,
+ val token: String?
)
typealias ConnectorSDKOnEventCallback = (ConnectorSDKEventType, ConnectorSDKCallbackMetadata) -> Unit
diff --git a/connector/src/main/java/app/quiltt/connector/QuilttConnectorWebView.kt b/connector/src/main/java/app/quiltt/connector/QuilttConnectorWebView.kt
index 83b9c10..a33a2e5 100644
--- a/connector/src/main/java/app/quiltt/connector/QuilttConnectorWebView.kt
+++ b/connector/src/main/java/app/quiltt/connector/QuilttConnectorWebView.kt
@@ -76,6 +76,7 @@ class QuilttConnectorWebViewClient(private val params: QuilttConnectorWebViewCli
val connectorId = params.config.connectorId
val profileId = urlComponents.getQueryParameter("profileId")
val connectionId = urlComponents.getQueryParameter("connectionId")
+ val token = urlComponents.getQueryParameter("token")
println("handleQuilttEvent $url")
when (url.host) {
"Load" -> {
@@ -83,19 +84,23 @@ class QuilttConnectorWebViewClient(private val params: QuilttConnectorWebViewCli
}
"ExitAbort" -> {
clearLocalStorage()
- params.onExit?.invoke(ConnectorSDKEventType.ExitAbort, ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null))
- params.onExitAbort?.invoke(ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null))
+ params.onExit?.invoke(ConnectorSDKEventType.ExitAbort, ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null, token = null))
+ params.onExitAbort?.invoke(ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null, token = null))
}
"ExitError" -> {
clearLocalStorage()
- params.onExit?.invoke(ConnectorSDKEventType.ExitError, ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null))
- params.onExitError?.invoke(ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null))
+ params.onExit?.invoke(ConnectorSDKEventType.ExitError, ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null, token = null))
+ params.onExitError?.invoke(ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = null, token = null))
}
"ExitSuccess" -> {
clearLocalStorage()
if (connectionId != null) {
- params.onExit?.invoke(ConnectorSDKEventType.ExitSuccess, ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = connectionId))
- params.onExitSuccess?.invoke(ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = connectionId))
+ params.onExit?.invoke(ConnectorSDKEventType.ExitSuccess, ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = connectionId, token = null))
+ params.onExitSuccess?.invoke(ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = null, connectionId = connectionId, token = null))
+ }
+ if (token != null) {
+ params.onExit?.invoke(ConnectorSDKEventType.ExitSuccess, ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = profileId, connectionId = null, token = token))
+ params.onExitSuccess?.invoke(ConnectorSDKCallbackMetadata(connectorId = connectorId, profileId = profileId, connectionId = null, token = token))
}
}
"Authenticate" -> {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index e78607a..7fa68c3 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -16,4 +16,4 @@ dependencyResolutionManagement {
rootProject.name = "quiltt-android"
include(":app")
include(":connector")
-include(":app_jetpack_compose")
+include(":app_quiltthub_webview_example")