Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
<trusted-key id="19BEAB2D799C020F17C69126B16698A4ADF4D638" group="org.checkerframework"/>
<trusted-key id="1A55F091AD28C07F831FA44D7905DE25C78AD456" group="com.google.protobuf"/>
<trusted-key id="1D0A8B5E77C678A7C724445ABF984B4145EA13F7" group="com.squareup" name="javapoet" version="1.13.0"/>
<trusted-key id="1D217F8475EEE9F19AB8DD6B793FD5751A0F0780" group="^com[.]squareup($|([.].*))" regex="true"/>
<trusted-key id="1D2C7EF8ADA0F794B58C7C63436902AF59EDF60E">
<trusting group="dev.equo.ide" name="solstice" version="1.7.5"/>
<trusting group="dev.equo.ide" name="solstice" version="1.8.0"/>
Expand Down Expand Up @@ -240,6 +241,7 @@
<trusting group="androidx.graphics" name="graphics-path" version="1.0.1"/>
<trusting group="androidx.lifecycle"/>
<trusting group="androidx.profileinstaller"/>
<trusting group="androidx.startup" name="startup-runtime" version="1.2.0"/>
<trusting group="androidx.transition" name="transition" version="1.5.0"/>
<trusting group="androidx.webkit"/>
<trusting group="^androidx[.]compose($|([.].*))" regex="true"/>
Expand Down Expand Up @@ -329,6 +331,11 @@
<sha256 value="c8923871e556cd5467addabac6773e778f3a4d3da19bfc8153bbaee0d145298f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-compose" version="1.7.0">
<artifact name="activity-compose-1.7.0.module">
<sha256 value="f7a29bcba338575dcf89a553cff9cfad3f140340eaf2b56fd0193244da602c0a" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-compose" version="1.8.2">
<artifact name="activity-compose-1.8.2.aar">
<sha256 value="5a67e984f14ed2afc585aa3a23edff1c1791c80caa2bf68a0f799c1b11a39038" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -20771,6 +20778,11 @@
<sha256 value="35bbd365a61afa59ad8c116528bced5bd628d3351704173abfc8b75fec04ffad" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin.plugin.serialization" name="org.jetbrains.kotlin.plugin.serialization.gradle.plugin" version="2.3.21">
<artifact name="org.jetbrains.kotlin.plugin.serialization.gradle.plugin-2.3.21.pom">
<sha256 value="d1e12aeedc9fab44409b6c08b2a45384a4bb851fe4e90391efade14d347c48d7" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="atomicfu" version="0.17.3">
<artifact name="atomicfu-0.17.3.module">
<sha256 value="854a75a9ebf30cb588e8ceda7da1b7089d4272a12324d3cffcaf5b62902738bd" origin="Generated by Gradle"/>
Expand Down
1 change: 1 addition & 0 deletions material-color-utilities/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

/*
* Nextcloud Android Common Library
*
Expand Down
1 change: 1 addition & 0 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.10.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
implementation project(path: ':ui')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
Expand Down
1 change: 1 addition & 0 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.Androidcommon">
<activity
android:name=".MainActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ class MainActivity : AppCompatActivity() {
material.colorMaterialButtonPrimaryBorderless(negativeButton)
}

binding.testApiBtn.setOnClickListener {
val baseUrl = binding.baseUrl.text?.toString().orEmpty()
val username = binding.username.text?.toString().orEmpty()
val token = binding.token.text?.toString().orEmpty()
mainViewModel.testPredefinedStatuses(baseUrl, username, token)
}

mainViewModel.apiTestResult.observe(this) { result ->
Toast.makeText(this, result, Toast.LENGTH_LONG).show()
}

setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
mainViewModel.color.observe(this) { applyTheme(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,41 @@ package com.nextcloud.android.common.sample

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nextcloud.android.common.ui.network.auth.ServerCredentials
import com.nextcloud.android.common.ui.network.model.NetworkResult
import com.nextcloud.android.common.ui.network.http.NextcloudHttpClient
import com.nextcloud.android.common.ui.network.UserStatusRepository
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {
val color = MutableLiveData<Int>()
val apiTestResult = MutableLiveData<String>()

fun testPredefinedStatuses(
baseUrl: String,
username: String,
token: String
) {
viewModelScope.launch {
val credentials = ServerCredentials(baseUrl, username, token)
val client = NextcloudHttpClient.create(credentials, enableLogging = true)
val service = UserStatusRepository(client)

when (val result = service.fetchPredefinedStatuses()) {
is NetworkResult.Success ->
apiTestResult.value =
"✅ Success (${result.data.size} statuses):\n" +
result.data.joinToString("\n") { "${it.icon} ${it.message}" }

is NetworkResult.ServerError ->
apiTestResult.value =
"❌ Error ${result.response.ocs.meta.statusCode}: ${result.response.ocs.meta.message}"

is NetworkResult.NetworkException ->
apiTestResult.value =
"❌ Exception: ${result.throwable.message}"
}
}
}
}
61 changes: 61 additions & 0 deletions sample/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,67 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/circular_progress_bar" />

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/baseUrlTil"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/hint_base_url"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dialogBtn">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/baseUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:text="https://cloud.example.com" />

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/usernameTil"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/hint_username"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/baseUrlTil">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tokenTil"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/hint_token"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/usernameTil">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/token"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.button.MaterialButton
android:id="@+id/testApiBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/test_user_status_api"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tokenTil" />

</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

Expand Down
4 changes: 4 additions & 0 deletions sample/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<string name="suggestion_chip">Suggestion Chip</string>
<string name="filter_chip">Filter Chip</string>
<string name="hint_color">Color</string>
<string name="hint_base_url">Base URL</string>
<string name="hint_username">Username</string>
<string name="hint_token">App token</string>
<string name="test_user_status_api">Test User Status API</string>
<string name="headline_theming">Theming</string>
<string name="headline_ui_module">UI Module</string>
</resources>
16 changes: 15 additions & 1 deletion ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

plugins {
id 'org.jetbrains.kotlin.plugin.compose'
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion"
id 'com.android.library'
id 'com.android.built-in-kotlin'
id 'com.android.legacy-kapt'
Expand All @@ -15,7 +16,7 @@ plugins {

android {
defaultConfig {
minSdk = 21
minSdk = 26
compileSdk = 36

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down Expand Up @@ -45,12 +46,19 @@ android {
}

dependencies {
implementation 'androidx.compose.ui:ui-tooling-preview:1.11.0'
debugImplementation 'androidx.compose.ui:ui-tooling:1.11.0'
kapt "org.jetbrains.kotlin:kotlin-metadata-jvm:${kotlinVersion}"

implementation(platform("androidx.compose:compose-bom:2026.05.01"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-core")

implementation("io.coil-kt.coil3:coil-compose:3.4.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0")
implementation("io.coil-kt.coil3:coil-svg:3.4.0")

implementation("com.vanniktech:ui:0.10.0")

Expand All @@ -60,6 +68,12 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'

implementation(platform("com.squareup.okhttp3:okhttp-bom:5.3.2"))
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")

implementation project(':core')
api project(':material-color-utilities')

Expand Down
2 changes: 1 addition & 1 deletion ui/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
~ SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
~ SPDX-License-Identifier: MIT
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Nextcloud Android Common Library
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: MIT
*/

package com.nextcloud.android.common.ui.component

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp

@Composable
fun ContentUnavailableView(title: String, description: String? = null, iconId: Int? = null) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
iconId?.let {
Image(
painter = painterResource(iconId),
modifier = Modifier.size(48.dp),
contentDescription = "",
)
}

Spacer(modifier = Modifier.height(16.dp))

Text(
text = title,
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center
)

description?.let {
Spacer(modifier = Modifier.height(8.dp))

Text(
text = description,
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center
)
}

Spacer(modifier = Modifier.height(32.dp))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Nextcloud Android Common Library
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: MIT
*/

package com.nextcloud.android.common.ui.network

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonPrimitive

object ClearAtTimeSerializer : KSerializer<String> {
override val descriptor = PrimitiveSerialDescriptor("ClearAtTime", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): String {
val jsonDecoder = decoder as? JsonDecoder ?: return decoder.decodeString()
return (jsonDecoder.decodeJsonElement() as? JsonPrimitive)?.content ?: ""
}

override fun serialize(encoder: Encoder, value: String) = encoder.encodeString(value)
}

@Serializable
data class ClearAt(
val type: String,
@Serializable(with = ClearAtTimeSerializer::class)
val time: String
)

@Serializable
data class PredefinedStatus(
val id: String,
val icon: String,
val message: String,
val clearAt: ClearAt? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Nextcloud Android Common Library
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: MIT
*/

package com.nextcloud.android.common.ui.network

import com.nextcloud.android.common.ui.network.http.HttpMethod
import com.nextcloud.android.common.ui.network.http.NextcloudHttpClient
import com.nextcloud.android.common.ui.network.model.NetworkResult
import com.nextcloud.android.common.ui.network.model.OcsResponse
import com.nextcloud.android.common.ui.network.serialization.OCSSerializer

class UserStatusRepository(private val client: NextcloudHttpClient) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For test activity


private companion object {
private const val PREDEFINED_STATUSES_ENDPOINT =
"/ocs/v2.php/apps/user_status/api/v1/predefined_statuses"
}

suspend fun fetchPredefinedStatuses(): NetworkResult<List<PredefinedStatus>> =
client.executeRequest(
endpoint = PREDEFINED_STATUSES_ENDPOINT,
method = HttpMethod.GET
) { body ->
OCSSerializer.json.decodeFromString<OcsResponse<List<PredefinedStatus>>>(body).ocs.data
}
}
Loading
Loading