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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ buildscript {
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.20' apply false
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion" apply false
id 'com.android.library' version '9.1.1' apply false
id 'org.jetbrains.kotlin.android' version "$kotlinVersion" apply false
id 'com.android.legacy-kapt' version '9.1.1' apply false
Expand Down
27 changes: 27 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,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 @@ -236,6 +237,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 @@ -324,6 +326,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 @@ -17849,6 +17856,11 @@
<sha256 value="6d535f94efb663bdb682c9f27a50335394688009642ba7a9677504bc1be4129b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jacoco" name="org.jacoco.build" version="0.8.14">
<artifact name="org.jacoco.build-0.8.14.pom">
<sha256 value="87d059b35849df97779148fd7e52f63f1fcd03a4e7e64c2a61a6c9cce61a8de7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jcommander" name="jcommander" version="1.85">
<artifact name="jcommander-1.85.jar">
<sha256 value="fa7552d2831a2b20778d86851d093edca68fbc0a77f792b6223110e4fae67a70" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
Expand Down Expand Up @@ -19234,6 +19246,11 @@
<sha256 value="2a4f7b44a3d0ac7aec7dbf5b1aa88db938bf542f7351561fb1d3b5cf81b60d05" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-gradle-plugins-bom" version="2.3.20">
<artifact name="kotlin-gradle-plugins-bom-2.3.20.pom">
<sha256 value="dd6b919197e132fe71ddac70f6fc5d8fae7f52777afd837b14de871b8c0a5400" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-klib-abi-reader" version="2.3.0">
<artifact name="kotlin-klib-abi-reader-2.3.0.jar">
<sha256 value="41e31422eb5eb224b9a475fe80eb8d06eb21ad34fb7d0a408d2c972a7abb8cbb" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -20699,6 +20716,11 @@
<sha256 value="8afdbb07964f0c3668a146ee4d8c90dab9d56737fa7627348ce757b686aedc13" 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.20">
<artifact name="org.jetbrains.kotlin.plugin.serialization.gradle.plugin-2.3.20.pom">
<sha256 value="379037f25c2de78ca3038481e4037601d697c86b175accaca764a9812230f170" 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 Expand Up @@ -21402,6 +21424,11 @@
<sha256 value="c5747e25c72e1b07d5263c75c78ad61a626dd2458fafcafc23f81d3253ee42ed" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2.asm" name="asm-bom" version="9.9">
<artifact name="asm-bom-9.9.pom">
<sha256 value="0fa27336fa7b62923ca9bdc69526ebe712a3bfb1f7ee8bf76c4a0b3f85f19d62" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.ow2.asm" name="asm-commons" version="9.6">
<artifact name="asm-commons-9.6.jar">
<sha256 value="7aefd0d5c0901701c69f7513feda765fb6be33af2ce7aa17c5781fc87657c511" 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
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,37 @@ 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.api.ApiCredentials
import com.nextcloud.android.common.ui.network.model.ApiResult
import com.nextcloud.android.common.ui.network.api.ApiHttpClient
import com.nextcloud.android.common.ui.network.UserStatusService
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 = ApiCredentials(baseUrl, username, token)
val client = ApiHttpClient.create(credentials, enableLogging = true)
val service = UserStatusService(client)

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

is ApiResult.Error ->
apiTestResult.value =
"❌ Error ${result.error.ocs.meta.statusCode}: ${result.error.ocs.meta.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>
10 changes: 10 additions & 0 deletions 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'
id 'com.android.library'
id 'com.android.built-in-kotlin'
id 'com.android.legacy-kapt'
Expand Down Expand Up @@ -45,12 +46,15 @@ android {
}

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

implementation(platform("androidx.compose:compose-bom:2026.03.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("com.vanniktech:ui:0.10.0")

Expand All @@ -60,6 +64,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.7.3")

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,24 @@
/*
* 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.Serializable

@Serializable
data class ClearAt(
val type: String,
val time: Int
)

@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,66 @@
/*
* 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.api.ApiHttpClient
import com.nextcloud.android.common.ui.network.model.ApiResult
import com.nextcloud.android.common.ui.network.model.Meta
import com.nextcloud.android.common.ui.network.model.Ocs
import com.nextcloud.android.common.ui.network.model.OcsResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okhttp3.Request

class UserStatusService(private val client: ApiHttpClient) {

private val json = Json { ignoreUnknownKeys = true }

suspend fun fetchPredefinedStatuses(): ApiResult<List<PredefinedStatus>> =
withContext(Dispatchers.IO) {
val url =
"${client.credentials.baseURL.trimEnd('/')}" +
"/ocs/v2.php/apps/user_status/api/v1/predefined_statuses"

val request =
Request
.Builder()
.url(url)
.header("Accept", "application/json")
.build()

try {
val response = client.okHttpClient.newCall(request).execute()
val body = response.body?.string().orEmpty()

if (response.isSuccessful) {
val parsed = json.decodeFromString<OcsResponse<List<PredefinedStatus>>>(body)
ApiResult.Success(parsed.ocs.data)
} else {
val error = json.decodeFromString<OcsResponse<String>>(body)
ApiResult.Error(error)
}
} catch (e: Exception) {
ApiResult.Error(
OcsResponse(
Ocs(
meta =
Meta(
status = "error",
statusCode = -1,
message = e.message ?: "Unknown error",
totalItems = "",
itemsPerPage = ""
),
data = e.message ?: "Unknown error"
)
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Nextcloud Android Common Library
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: MIT
*/

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

data class ApiCredentials(
val baseURL: String,
val username: String,
val token: String
)
Loading