feat: add platform OAuth implementations (Android, iOS, Desktop)

- AndroidOAuthClient: ACTION_VIEW + OAuthCallbackActivity (myice://callback)
- IosOAuthClient: ASWebAuthenticationSession with presentation context provider
- DesktopOAuthClient: local HTTP server + system browser
- AndroidManifest: register OAuthCallbackActivity intent filter
- Align Kotlin to 2.3.20 (required by Compose MP 1.11.1 klibs) and
  compileSdk to 36 (required by activity-compose 1.13.0); fix missing
  Text import and stray brace in placeholder iOS/Android entry points
This commit is contained in:
2026-07-08 17:42:44 +02:00
parent 294aebd0b4
commit 5dc206ae72
9 changed files with 227 additions and 3 deletions
@@ -15,6 +15,17 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".OAuthCallbackActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myice" android:host="callback" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -4,6 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.Text
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -0,0 +1,17 @@
package ch.parano.myice
import android.app.Activity
import android.net.Uri
import android.os.Bundle
import ch.parano.myice.auth.OAuthCallbackHolder
class OAuthCallbackActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri: Uri? = intent?.data
if (uri != null) {
OAuthCallbackHolder.handleCallback(uri)
}
finish()
}
}
@@ -0,0 +1,39 @@
package ch.parano.myice.auth
import android.content.Context
import android.content.Intent
import android.net.Uri
import kotlinx.coroutines.CompletableDeferred
class AndroidOAuthClient(private val context: Context) : OAuthClient {
override fun redirectUri(): String = "myice://callback"
override suspend fun authenticate(loginUrl: String): String? {
OAuthCallbackHolder.deferred = CompletableDeferred()
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(loginUrl)).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
val callbackUri = OAuthCallbackHolder.deferred?.await() ?: return null
val fragment = callbackUri.fragment ?: return null
val params = fragment.split("&").associate { pair ->
val idx = pair.indexOf("=")
if (idx >= 0) pair.substring(0, idx) to pair.substring(idx + 1)
else pair to ""
}
return params["access_token"]
}
}
object OAuthCallbackHolder {
var deferred: CompletableDeferred<Uri>? = null
fun handleCallback(uri: Uri) {
deferred?.complete(uri)
deferred = null
}
}