Port Nextcloud login UI to compose
This commit is contained in:
parent
9822777acb
commit
4485b59ade
@ -54,7 +54,6 @@
|
|||||||
android:label="@string/settings"
|
android:label="@string/settings"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:parentActivityName=".launcher.LauncherActivity"
|
android:parentActivityName=".launcher.LauncherActivity"
|
||||||
android:taskAffinity="de.mm20.launcher2.settings"
|
|
||||||
android:theme="@style/SettingsTheme.NoActionBar"
|
android:theme="@style/SettingsTheme.NoActionBar"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ pluginSdk = "2.2.0-SNAPSHOT"
|
|||||||
|
|
||||||
gradle = "8.1.2"
|
gradle = "8.1.2"
|
||||||
android-gradle-plugin = "8.6.1"
|
android-gradle-plugin = "8.6.1"
|
||||||
ksp-gradle-plugin = "2.1.10-1.0.29"
|
ksp-gradle-plugin = "2.1.20-2.0.0"
|
||||||
|
|
||||||
kotlin = "2.1.20"
|
kotlin = "2.1.20"
|
||||||
kotlinx-coroutines = "1.9.0"
|
kotlinx-coroutines = "1.9.0"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.library)
|
alias(libs.plugins.android.library)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.plugin.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -28,7 +29,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
compose = true
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@ -39,11 +40,11 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.bundles.kotlin)
|
implementation(libs.bundles.kotlin)
|
||||||
|
implementation(libs.androidx.activitycompose)
|
||||||
|
implementation(libs.androidx.compose.material3)
|
||||||
implementation(libs.androidx.core)
|
implementation(libs.androidx.core)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.materialcomponents.core)
|
|
||||||
implementation(libs.androidx.browser)
|
implementation(libs.androidx.browser)
|
||||||
implementation(libs.androidx.constraintlayout.views)
|
|
||||||
implementation(libs.androidx.securitycrypto)
|
implementation(libs.androidx.securitycrypto)
|
||||||
|
|
||||||
implementation(libs.bundles.androidx.lifecycle)
|
implementation(libs.bundles.androidx.lifecycle)
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
<application>
|
<application>
|
||||||
<activity
|
<activity
|
||||||
|
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||||
android:name="de.mm20.launcher2.nextcloud.LoginActivity"
|
android:name="de.mm20.launcher2.nextcloud.LoginActivity"
|
||||||
android:label="@string/preference_nextcloud"
|
android:label="@string/preference_nextcloud"
|
||||||
android:taskAffinity="de.mm20.launcher2.nextcloud"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/NextcloudLoginTheme" />
|
android:enableOnBackInvokedCallback="true"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@ -2,41 +2,135 @@ package de.mm20.launcher2.nextcloud
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ColorScheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import de.mm20.launcher2.nextcloud.databinding.ActivityNextcloudLoginBinding
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.*
|
|
||||||
|
|
||||||
class LoginActivity : AppCompatActivity() {
|
class LoginActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val nextcloudClient = NextcloudApiHelper(this)
|
private val nextcloudClient = NextcloudApiHelper(this)
|
||||||
|
|
||||||
private lateinit var binding: ActivityNextcloudLoginBinding
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityNextcloudLoginBinding.inflate(LayoutInflater.from(this))
|
|
||||||
setContentView(binding.root)
|
setContent {
|
||||||
binding.nextButton.setOnClickListener {
|
MaterialTheme(
|
||||||
binding.serverUrlInputLayout.error = null
|
colorScheme = if (isSystemInDarkTheme()) nextcloudDark else nextcloudLight
|
||||||
|
) {
|
||||||
|
var nextcloudUrl by remember { mutableStateOf("") }
|
||||||
|
var error by remember { mutableStateOf<String?>(null) }
|
||||||
|
var loading by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val dark = isSystemInDarkTheme()
|
||||||
|
|
||||||
|
LaunchedEffect(dark) {
|
||||||
|
enableEdgeToEdge(
|
||||||
|
statusBarStyle = if (dark) SystemBarStyle.dark(0) else SystemBarStyle.light(
|
||||||
|
0,
|
||||||
|
0x33000000.toInt()
|
||||||
|
),
|
||||||
|
navigationBarStyle = if (dark) SystemBarStyle.dark(0) else SystemBarStyle.light(
|
||||||
|
0,
|
||||||
|
0x33000000.toInt()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.padding(32.dp)
|
||||||
|
.systemBarsPadding()
|
||||||
|
.imePadding(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painterResource(R.drawable.ic_nextcloud_logo),
|
||||||
|
contentDescription = "Nextcloud Logo",
|
||||||
|
colorFilter = ColorFilter.tint(
|
||||||
|
MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 32.dp),
|
||||||
|
label = {
|
||||||
|
Text(stringResource(R.string.nextcloud_server_url))
|
||||||
|
},
|
||||||
|
value = nextcloudUrl,
|
||||||
|
onValueChange = { nextcloudUrl = it },
|
||||||
|
enabled = !loading,
|
||||||
|
isError = error != null,
|
||||||
|
supportingText = error?.let { { Text(it) } },
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Uri,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
enabled = nextcloudUrl.isNotBlank() && !loading,
|
||||||
|
onClick = {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var url = binding.serverUrlInput.text.toString()
|
loading = true
|
||||||
|
error = null
|
||||||
|
var url = nextcloudUrl
|
||||||
if (!(url.startsWith("http://") || url.startsWith("https://"))) {
|
if (!(url.startsWith("http://") || url.startsWith("https://"))) {
|
||||||
url = "https://$url"
|
url = "https://$url"
|
||||||
}
|
}
|
||||||
if (url.isBlank()) {
|
|
||||||
binding.serverUrlInputLayout.error = getString(R.string.nextcloud_server_url_empty)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
if (nextcloudClient.checkNextcloudInstallation(url)) {
|
if (nextcloudClient.checkNextcloudInstallation(url)) {
|
||||||
openLoginPage(url)
|
openLoginPage(url)
|
||||||
} else {
|
} else {
|
||||||
binding.serverUrlInputLayout.error = getString(R.string.nextcloud_server_invalid_url)
|
error = getString(R.string.nextcloud_server_invalid_url)
|
||||||
}
|
}
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.login_flow_continue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,7 +139,10 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
val webView = WebView(this)
|
val webView = WebView(this)
|
||||||
webView.settings.userAgentString = getString(R.string.app_name)
|
webView.settings.userAgentString = getString(R.string.app_name)
|
||||||
webView.webViewClient = object : WebViewClient() {
|
webView.webViewClient = object : WebViewClient() {
|
||||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
override fun shouldOverrideUrlLoading(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?
|
||||||
|
): Boolean {
|
||||||
if (request?.url?.scheme == "nc") {
|
if (request?.url?.scheme == "nc") {
|
||||||
val path = request.url?.path?.trim('/') ?: run {
|
val path = request.url?.path?.trim('/') ?: run {
|
||||||
setResult(0)
|
setResult(0)
|
||||||
@ -82,4 +179,76 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
webView.loadUrl("$url/index.php/login/flow", headers)
|
webView.loadUrl("$url/index.php/login/flow", headers)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val nextcloudLight: ColorScheme
|
||||||
|
get() = lightColorScheme(
|
||||||
|
primary = Color(0xFF00639B),
|
||||||
|
onPrimary = Color(0xFFFFFFFF),
|
||||||
|
primaryContainer = Color(0xFFCEE5FF),
|
||||||
|
onPrimaryContainer = Color(0xFF001D33),
|
||||||
|
inversePrimary = Color(0xFF96CBFF),
|
||||||
|
secondary = Color(0xFF51606F),
|
||||||
|
onSecondary = Color(0xFFFFFFFF),
|
||||||
|
secondaryContainer = Color(0xFFD4E4F6),
|
||||||
|
onSecondaryContainer = Color(0xFF0D1D2A),
|
||||||
|
tertiary = Color(0xFF68587A),
|
||||||
|
onTertiary = Color(0xFFFFFFFF),
|
||||||
|
tertiaryContainer = Color(0xFFEEDBFF),
|
||||||
|
onTertiaryContainer = Color(0xFF231533),
|
||||||
|
surface = Color(0xFFF9F9FC),
|
||||||
|
surfaceBright = Color(0xFFF9F9FC),
|
||||||
|
surfaceDim = Color(0xFFD9DADD),
|
||||||
|
surfaceContainer = Color(0xFFEDEEF1),
|
||||||
|
surfaceContainerHighest = Color(0xFFE2E2E5),
|
||||||
|
surfaceContainerHigh = Color(0xFFE8E8EB),
|
||||||
|
surfaceContainerLow = Color(0xFFF3F3F6),
|
||||||
|
surfaceContainerLowest = Color(0xFFFFFFFF),
|
||||||
|
onSurface = Color(0xFF1A1C1E),
|
||||||
|
onSurfaceVariant = Color(0xFF42474E),
|
||||||
|
inverseSurface = Color(0xFF2F3133),
|
||||||
|
inverseOnSurface = Color(0xFFF0F0F3),
|
||||||
|
error = Color(0xFFBA1A1A),
|
||||||
|
onError = Color(0xFFFFFFFF),
|
||||||
|
errorContainer = Color(0xFFFFDAD5),
|
||||||
|
onErrorContainer = Color(0xFF410002),
|
||||||
|
outline = Color(0xFF72787F),
|
||||||
|
outlineVariant = Color(0xFFC2C7CF),
|
||||||
|
scrim = Color(0xFF000000),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val nextcloudDark: ColorScheme
|
||||||
|
get() = darkColorScheme(
|
||||||
|
primary = Color(0xFF96CBFF),
|
||||||
|
onPrimary = Color(0xFF003353),
|
||||||
|
primaryContainer = Color(0xFF004A76),
|
||||||
|
onPrimaryContainer = Color(0xFFCEE5FF),
|
||||||
|
inversePrimary = Color(0xFF00639B),
|
||||||
|
secondary = Color(0xFFB9C8DA),
|
||||||
|
onSecondary = Color(0xFF23323F),
|
||||||
|
secondaryContainer = Color(0xFF394857),
|
||||||
|
onSecondaryContainer = Color(0xFFD4E4F6),
|
||||||
|
tertiary = Color(0xFFD3BFE6),
|
||||||
|
onTertiary = Color(0xFF382A49),
|
||||||
|
tertiaryContainer = Color(0xFF4F4061),
|
||||||
|
onTertiaryContainer = Color(0xFFEEDBFF),
|
||||||
|
surface = Color(0xFF1A1C1E),
|
||||||
|
surfaceBright = Color(0xFF37393C),
|
||||||
|
surfaceDim = Color(0xFF111416),
|
||||||
|
surfaceContainer = Color(0xFF1E2022),
|
||||||
|
surfaceContainerHighest = Color(0xFF333537),
|
||||||
|
surfaceContainerHigh = Color(0xFF282A2D),
|
||||||
|
surfaceContainerLow = Color(0xFF1A1C1E),
|
||||||
|
surfaceContainerLowest = Color(0xFF0C0E11),
|
||||||
|
onSurface = Color(0xFFE2E2E5),
|
||||||
|
onSurfaceVariant = Color(0xFFC2C7CF),
|
||||||
|
inverseSurface = Color(0xFFE2E2E5),
|
||||||
|
inverseOnSurface = Color(0xFF2F3133),
|
||||||
|
error = Color(0xFFFFB4AB),
|
||||||
|
onError = Color(0xFF690004),
|
||||||
|
errorContainer = Color(0xFF930009),
|
||||||
|
onErrorContainer = Color(0xFFFFB4AB),
|
||||||
|
outline = Color(0xFF8C9198),
|
||||||
|
outlineVariant = Color(0xFF42474E),
|
||||||
|
scrim = Color(0xFF000000),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_margin="32dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:srcCompat="@drawable/ic_nextcloud_logo" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/serverUrlInputLayout"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:layout_marginBottom="32dp"
|
|
||||||
android:hint="@string/nextcloud_server_url"
|
|
||||||
app:helperTextEnabled="true">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/serverUrlInput"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textUri" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/nextButton"
|
|
||||||
style="@style/Widget.MaterialComponents.Button"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/login_flow_continue" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</FrameLayout>
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<style name="NextcloudLoginTheme" parent="Theme.MaterialComponents.NoActionBar">
|
|
||||||
<item name="colorAccent">#0082c9</item>
|
|
||||||
<item name="colorPrimary">#0082c9</item>
|
|
||||||
<item name="colorPrimaryDark">@color/settings_color_primary_dark</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<style name="NextcloudLoginTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
|
||||||
<item name="android:windowLightNavigationBar">true</item>
|
|
||||||
<item name="colorAccent">#0082c9</item>
|
|
||||||
<item name="colorPrimary">#0082c9</item>
|
|
||||||
<item name="colorPrimaryDark">@color/settings_color_primary_dark</item>
|
|
||||||
<item name="android:navigationBarColor">@color/settings_color_primary_dark</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user