diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..acfb8bb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# https://editorconfig.org/ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 9be5041..2d2a625 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -4,14 +4,14 @@ on: # Push on main and release tags push: branches: - - 'main' + - "main" tags: - - '[0-9]+.[0-9]+.[0-9]+' + - "[0-9]+.[0-9]+.[0-9]+" # PR pull_request: branches: - - 'main' + - "main" # Manual trigger workflow_dispatch: @@ -21,7 +21,6 @@ concurrency: cancel-in-progress: true jobs: - build: name: CI/CD runs-on: ubuntu-latest @@ -34,9 +33,9 @@ jobs: id: java uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: - distribution: 'temurin' - java-version: '17' - cache: 'gradle' + distribution: "temurin" + java-version: "17" + cache: "gradle" - name: Check if main repo id: is_main_repo @@ -45,6 +44,9 @@ jobs: echo "value=true" >> $GITHUB_OUTPUT fi + - name: Run Spotless check + run: ./gradlew spotlessCheck + - name: Run assemble if: ${{ !steps.is_main_repo.outputs.value || github.event_name == 'pull_request' }} run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4afe8ad..0fcb4b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: inputs: VERSION_INCREMENT: - description: 'The version number part to increment (major.minor.patch)' + description: "The version number part to increment (major.minor.patch)" default: Minor type: choice options: @@ -14,32 +14,32 @@ on: - Patch required: false VERSION_OVERRIDE: - description: 'Version override (when not incrementing the previous version)' + description: "Version override (when not incrementing the previous version)" type: string required: false - permissions: actions: write contents: write jobs: - build: name: Release runs-on: ubuntu-latest steps: - - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK 17 and gradle cache uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: - distribution: 'temurin' - java-version: '17' - cache: 'gradle' + distribution: "temurin" + java-version: "17" + cache: "gradle" + + - name: Run Spotless check + run: ./gradlew spotlessCheck - name: Run release id: release diff --git a/GenericApp/app/build.gradle b/GenericApp/app/build.gradle index 3057a48..768d91e 100644 --- a/GenericApp/app/build.gradle +++ b/GenericApp/app/build.gradle @@ -1,155 +1,155 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.google.services) - id 'maven-publish' - id 'signing' + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) + id 'maven-publish' + id 'signing' } android { - defaultConfig { - applicationId = "io.openremote.app" - minSdk = 25 - compileSdk = 37 - targetSdk = 37 - versionCode = 47 - versionName = version - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - resValue "string", "NOTIFICATION_CHANNEL_ID", applicationId + ".ORAppMessage" - } - - if (project.hasProperty("keystoreFile")) { - signingConfigs { - release { - keyAlias keystoreKeyAlias - keyPassword keystoreKeyPassword - storeFile file(keystoreFile) - storePassword keystorePassword - } - } + defaultConfig { + applicationId = "io.openremote.app" + minSdk = 25 + compileSdk = 37 + targetSdk = 37 + versionCode = 47 + versionName = version + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + resValue "string", "NOTIFICATION_CHANNEL_ID", applicationId + ".ORAppMessage" + } + + if (project.hasProperty("keystoreFile")) { + signingConfigs { + release { + keyAlias keystoreKeyAlias + keyPassword keystoreKeyPassword + storeFile file(keystoreFile) + storePassword keystorePassword + } } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - if (project.hasProperty("keystoreFile")) { - signingConfig signingConfigs.release - } - } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + if (project.hasProperty("keystoreFile")) { + signingConfig signingConfigs.release + } } + } - buildFeatures { - viewBinding = true - resValues = true - } + buildFeatures { + viewBinding = true + resValues = true + } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } - namespace = 'io.openremote.app' + namespace = 'io.openremote.app' - publishing { - singleVariant('release') { - } + publishing { + singleVariant('release') { } + } } kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } } dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation libs.androidx.core.ktx - implementation libs.androidx.appcompat - implementation libs.androidx.constraintlayout - implementation libs.androidx.preference.ktx - implementation libs.play.services.location - implementation libs.jackson.module.kotlin - implementation platform(libs.firebase.bom) - implementation libs.firebase.messaging - implementation libs.material - implementation project(':ORLib') - - // Unit testing framework - testImplementation libs.junit + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation libs.androidx.core.ktx + implementation libs.androidx.appcompat + implementation libs.androidx.constraintlayout + implementation libs.androidx.preference.ktx + implementation libs.play.services.location + implementation libs.jackson.module.kotlin + implementation platform(libs.firebase.bom) + implementation libs.firebase.messaging + implementation libs.material + implementation project(':ORLib') + + // Unit testing framework + testImplementation libs.junit } tasks.register('sourcesJar', Jar) { - from android.sourceSets.main.java.srcDirs - archiveClassifier = 'sources' + from android.sourceSets.main.java.srcDirs + archiveClassifier = 'sources' } afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - - artifact source: file('build/outputs/apk/release/app-release.apk') - artifact sourcesJar - - group = "io.openremote" - artifactId = "app" - - pom { - name = 'OpenRemote Android Console' - description = 'Provides the Android OpenRemote Console Application' - url = 'https://github.com/openremote/console-android' - licenses { - license { - name = 'GNU Affero General Public License v3.0' - url = 'https://www.gnu.org/licenses/agpl-3.0.en.html' - } - } - developers { - developer { - id = 'developers' - name = 'Developers' - email = 'developers@openremote.io' - organization = 'OpenRemote' - organizationUrl = 'https://openremote.io' - } - } - scm { - connection = 'scm:git:git://github.com/openremote/console-android.git' - developerConnection = 'scm:git:ssh://github.com:openremote/console-android.git' - url = 'https://github.com/openremote/console-android/tree/main' - } - } + publishing { + publications { + release(MavenPublication) { + from components.release + + artifact source: file('build/outputs/apk/release/app-release.apk') + artifact sourcesJar + + group = "io.openremote" + artifactId = "app" + + pom { + name = 'OpenRemote Android Console' + description = 'Provides the Android OpenRemote Console Application' + url = 'https://github.com/openremote/console-android' + licenses { + license { + name = 'GNU Affero General Public License v3.0' + url = 'https://www.gnu.org/licenses/agpl-3.0.en.html' } - } - - repositories { - maven { - if (!version.endsWith('-LOCAL')) { - credentials { - username = findProperty("publishUsername") - password = findProperty("publishPassword") - } - } - url = version.endsWith('-LOCAL') ? layout.buildDirectory.dir('repo') : version.endsWith('-SNAPSHOT') ? findProperty("snapshotsRepoUrl") : findProperty("releasesRepoUrl") + } + developers { + developer { + id = 'developers' + name = 'Developers' + email = 'developers@openremote.io' + organization = 'OpenRemote' + organizationUrl = 'https://openremote.io' } + } + scm { + connection = 'scm:git:git://github.com/openremote/console-android.git' + developerConnection = 'scm:git:ssh://github.com:openremote/console-android.git' + url = 'https://github.com/openremote/console-android/tree/main' + } } + } } - signing { - def signingKey = findProperty("signingKey") - def signingPassword = findProperty("signingPassword") - if (signingKey && signingPassword) { - useInMemoryPgpKeys(signingKey, signingPassword) - sign publishing.publications - signReleasePublication.dependsOn(packageRelease) + repositories { + maven { + if (!version.endsWith('-LOCAL')) { + credentials { + username = findProperty("publishUsername") + password = findProperty("publishPassword") + } } + url = version.endsWith('-LOCAL') ? layout.buildDirectory.dir('repo') : version.endsWith('-SNAPSHOT') ? findProperty("snapshotsRepoUrl") : findProperty("releasesRepoUrl") + } + } + } + + signing { + def signingKey = findProperty("signingKey") + def signingPassword = findProperty("signingPassword") + if (signingKey && signingPassword) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign publishing.publications + signReleasePublication.dependsOn(packageRelease) } + } - publishReleasePublicationToMavenLocal.dependsOn(packageRelease) + publishReleasePublicationToMavenLocal.dependsOn(packageRelease) } diff --git a/GenericApp/app/google-services.json b/GenericApp/app/google-services.json index 75b2bc9..28a71ed 100644 --- a/GenericApp/app/google-services.json +++ b/GenericApp/app/google-services.json @@ -27,4 +27,4 @@ } ], "configuration_version": "1" -} \ No newline at end of file +} diff --git a/GenericApp/app/proguard-rules.pro b/GenericApp/app/proguard-rules.pro index 481bb43..f1b4245 100644 --- a/GenericApp/app/proguard-rules.pro +++ b/GenericApp/app/proguard-rules.pro @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile diff --git a/GenericApp/app/src/main/AndroidManifest.xml b/GenericApp/app/src/main/AndroidManifest.xml index 34f5bd1..d96a605 100644 --- a/GenericApp/app/src/main/AndroidManifest.xml +++ b/GenericApp/app/src/main/AndroidManifest.xml @@ -1,88 +1,59 @@ - - - - - - - - - + + + + + + + + + - - - - + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + diff --git a/GenericApp/app/src/main/java/io/openremote/app/model/ORAppInfo.kt b/GenericApp/app/src/main/java/io/openremote/app/model/ORAppInfo.kt index 9737138..9cfd286 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/model/ORAppInfo.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/model/ORAppInfo.kt @@ -1,8 +1,26 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.model data class ORAppInfo( - val consoleAppIncompatible: Boolean, - val realms: List, - val providers: List, - val description: String + val consoleAppIncompatible: Boolean, + val realms: List, + val providers: List, + val description: String, ) diff --git a/GenericApp/app/src/main/java/io/openremote/app/model/ORConsoleConfig.kt b/GenericApp/app/src/main/java/io/openremote/app/model/ORConsoleConfig.kt index ec320a9..be40cdc 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/model/ORConsoleConfig.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/model/ORConsoleConfig.kt @@ -1,9 +1,27 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.model -data class ORConsoleConfig ( - val showAppTextInput: Boolean, - val showRealmTextInput: Boolean, - val app: String?, - val allowedApps: List?, - val apps: Map? +data class ORConsoleConfig( + val showAppTextInput: Boolean, + val showRealmTextInput: Boolean, + val app: String?, + val allowedApps: List?, + val apps: Map?, ) diff --git a/GenericApp/app/src/main/java/io/openremote/app/model/ProjectItem.kt b/GenericApp/app/src/main/java/io/openremote/app/model/ProjectItem.kt index 943fe24..bce670f 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/model/ProjectItem.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/model/ProjectItem.kt @@ -1,8 +1,26 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.model -data class ProjectItem ( - val host: String, - val app: String, - val realm: String?, - val url: String +data class ProjectItem( + val host: String, + val app: String, + val realm: String?, + val url: String, ) diff --git a/GenericApp/app/src/main/java/io/openremote/app/network/ApiManager.kt b/GenericApp/app/src/main/java/io/openremote/app/network/ApiManager.kt index 2032150..cc4460a 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/network/ApiManager.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/network/ApiManager.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.network import android.net.Uri @@ -8,137 +26,141 @@ import io.openremote.app.model.ORAppInfo import io.openremote.app.model.ORConsoleConfig import java.net.HttpURLConnection import java.net.URL -import javax.net.ssl.HttpsURLConnection import kotlin.concurrent.thread - typealias ResponseBlock = (statusCode: Int, model: T?, error: Throwable?) -> Unit class ApiManager(private val baseUrl: String) { - val mapper = jacksonObjectMapper() - - enum class HttpMethod { - GET, - POST, - PUT, + val mapper = jacksonObjectMapper() + + enum class HttpMethod { + GET, + POST, + PUT, + } + + fun getApps(callback: ResponseBlock>?) { + get(arrayOf("apps"), callback) + } + + fun getAppInfos(callback: ResponseBlock>?) { + get(arrayOf("apps", "info"), callback) + } + + fun getConsoleConfig(callback: ResponseBlock?) { + get(arrayOf("apps", "consoleConfig"), callback) + } + + /** *******************************Private functions****************************** */ + fun createUrlRequest( + method: HttpMethod, + pathComponents: Array, + queryParameters: Map? = null, + ): HttpURLConnection { + val builder = Uri.parse(baseUrl).buildUpon() + + for (pathComponent in pathComponents) builder.appendPath(pathComponent) + + if (queryParameters != null) { + for ((queryParameterKey, queryParameterValue) in queryParameters) builder + .appendQueryParameter( + queryParameterKey, + queryParameterValue.toString(), + ) } - fun getApps(callback: ResponseBlock>?) { - get(arrayOf("apps"), callback) - } + val url = URL(builder.build().toString()) + with(url.openConnection() as HttpURLConnection) { + requestMethod = method.toString() + setRequestProperty("Accept", "application/json") - fun getAppInfos(callback: ResponseBlock>?) { - get(arrayOf("apps", "info"), callback) - } + if (method == HttpMethod.POST || method == HttpMethod.PUT) { + setRequestProperty("Content-Type", "application/json") + doOutput = true + } - fun getConsoleConfig(callback: ResponseBlock?) { - get(arrayOf("apps", "consoleConfig"), callback) + return this } - - /*********************************Private functions*******************************/ - fun createUrlRequest( - method: HttpMethod, - pathComponents: Array, - queryParameters: Map? = null - ): HttpURLConnection { - val builder = Uri.parse(baseUrl).buildUpon() - - for (pathComponent in pathComponents) builder.appendPath(pathComponent) - - if (queryParameters != null) { - for ((queryParameterKey, queryParameterValue) in queryParameters) - builder.appendQueryParameter( - queryParameterKey, - queryParameterValue.toString() - ) + } + + inline fun get( + pathComponents: Array, + noinline callback: ResponseBlock?, + queryParameters: Map? = null, + ) { + thread(start = true) { + val urlRequest = createUrlRequest(HttpMethod.GET, pathComponents, queryParameters) + parseResponse(urlRequest, callback) + } + } + + private inline fun post( + pathComponents: Array, + item: Any, + noinline callback: ResponseBlock?, + ) { + thread(start = true) { + val urlRequest = createUrlRequest(HttpMethod.POST, pathComponents) + urlRequest + .apply { + writeBody(item) } - - val url = URL(builder.build().toString()) - with(url.openConnection() as HttpURLConnection) { - requestMethod = method.toString() - setRequestProperty("Accept", "application/json") - - if (method == HttpMethod.POST || method == HttpMethod.PUT) { - setRequestProperty("Content-Type", "application/json") - doOutput = true - } - - return this + .let { httpConnection -> + parseResponse(httpConnection, callback) } } - - inline fun get( - pathComponents: Array, - noinline callback: ResponseBlock?, - queryParameters: Map? = null - ) { - thread(start = true) { - val urlRequest = createUrlRequest(HttpMethod.GET, pathComponents, queryParameters) - parseResponse(urlRequest, callback) + } + + private inline fun put( + pathComponents: Array, + item: Any, + noinline callback: ResponseBlock?, + ) { + thread(start = true) { + val urlRequest = createUrlRequest(HttpMethod.PUT, pathComponents) + urlRequest + .apply { + writeBody(item) } - } - - - private inline fun post( - pathComponents: Array, item: Any, noinline callback: ResponseBlock? - ) { - thread(start = true) { - val urlRequest = createUrlRequest(HttpMethod.POST, pathComponents) - urlRequest.apply { - writeBody(item) - }.let { httpConnection -> - parseResponse(httpConnection, callback) - } + .let { httpConnection -> + parseResponse(httpConnection, callback) } } - - private inline fun put( - pathComponents: Array, item: Any, noinline callback: ResponseBlock? - ) { - thread(start = true) { - val urlRequest = createUrlRequest(HttpMethod.PUT, pathComponents) - urlRequest.apply { - writeBody(item) - }.let { httpConnection -> - parseResponse(httpConnection, callback) - } + } + + inline fun parseResponse( + httpConnection: HttpURLConnection, + noinline callback: ResponseBlock?, + ) { + with(httpConnection) { + val parsedResult = + try { + if (this.responseCode in 200..299) { + Triple( + this.responseCode, + mapper.readValue(this.inputStream.bufferedReader().readText()), + null, + ) + } else { + val errorResponse = this.errorStream.bufferedReader().readText() + Triple(this.responseCode, null, Throwable(errorResponse)) + } + } catch (e: Exception) { + Triple(0, null, e) } - } - inline fun parseResponse( - httpConnection: HttpURLConnection, - noinline callback: ResponseBlock? - ) { - with(httpConnection) { - val parsedResult = try { - if (this.responseCode in 200..299) { - Triple( - this.responseCode, mapper.readValue( - this.inputStream.bufferedReader().readText() - ), null - ) - } else { - val errorResponse = this.errorStream.bufferedReader().readText() - Triple(this.responseCode, null, Throwable(errorResponse)) - } - } catch (e: Exception) { - Triple(0, null, e) - } - - parsedResult.third?.let { - Log.e(this::class.simpleName, it.message, it) - } - callback?.invoke(parsedResult.first, parsedResult.second, parsedResult.third) - this.disconnect() - } + parsedResult.third?.let { + Log.e(this::class.simpleName, it.message, it) + } + callback?.invoke(parsedResult.first, parsedResult.second, parsedResult.third) + this.disconnect() } + } - private fun HttpURLConnection.writeBody(item: Any) { - val outputWriter = outputStream.bufferedWriter() - outputWriter.write( - mapper.writeValueAsString(item) - ) - outputWriter.flush() - } + private fun HttpURLConnection.writeBody(item: Any) { + val outputWriter = outputStream.bufferedWriter() + outputWriter.write(mapper.writeValueAsString(item)) + outputWriter.flush() + } } diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/AppSelectionFragment.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/AppSelectionFragment.kt index c17b722..6daa844 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/AppSelectionFragment.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/AppSelectionFragment.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.ui import android.os.Bundle @@ -20,234 +38,241 @@ private const val ARG_SHOW_APP_TEXT_INPUT = "showAppTextInput" private const val ARG_SHOW_REALM_TEXT_INPUT = "showRealmTextInput" /** - * A simple [Fragment] subclass. - * Use the [AppSelectionFragment.newInstance] factory method to - * create an instance of this fragment. + * A simple [Fragment] subclass. Use the [AppSelectionFragment.newInstance] factory method to create + * an instance of this fragment. */ class AppSelectionFragment : Fragment() { - private lateinit var binding: FragmentAppSelectionBinding - private lateinit var parentActivity: ProjectWizardActivity + private lateinit var binding: FragmentAppSelectionBinding + private lateinit var parentActivity: ProjectWizardActivity - lateinit var appArrayAdapter: ArrayAdapter + lateinit var appArrayAdapter: ArrayAdapter - private var appList: List? = null - private var appMap: Map? = null - private var showAppTextInput: Boolean = false - private var showRealmTextInput: Boolean = false - private val mapper = jacksonObjectMapper() + private var appList: List? = null + private var appMap: Map? = null + private var showAppTextInput: Boolean = false + private var showRealmTextInput: Boolean = false + private val mapper = jacksonObjectMapper() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - parentActivity = requireActivity() as ProjectWizardActivity + parentActivity = requireActivity() as ProjectWizardActivity - arguments?.let { - it.getString(ARG_APP_LIST)?.let { - appList = mapper.readValue>(it) - } - it.getString(ARG_APP_MAP)?.let { - appMap = mapper.readValue>(it) - } - showAppTextInput = it.getBoolean(ARG_SHOW_APP_TEXT_INPUT, false) - showRealmTextInput = it.getBoolean(ARG_SHOW_REALM_TEXT_INPUT, false) - } + arguments?.let { + it.getString(ARG_APP_LIST)?.let { + appList = mapper.readValue>(it) + } + it.getString(ARG_APP_MAP)?.let { + appMap = mapper.readValue>(it) + } + showAppTextInput = it.getBoolean(ARG_SHOW_APP_TEXT_INPUT, false) + showRealmTextInput = it.getBoolean(ARG_SHOW_REALM_TEXT_INPUT, false) + } - if (!appList.isNullOrEmpty()) { - if (appList!!.size == 1) { - parentActivity.app = appList!![0] - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - RealmSelectionFragment.newInstance( - realmList = null, - showRealmTextInput = showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } - } + if (!appList.isNullOrEmpty()) { + if (appList!!.size == 1) { + parentActivity.app = appList!![0] + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + RealmSelectionFragment.newInstance( + realmList = null, + showRealmTextInput = showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } + } - if (!appMap.isNullOrEmpty()) { - if (appMap!!.size == 1) { - parentActivity.app = appMap!!.keys.elementAt(0) - parentActivity.consoleProviders = appMap!!.values.elementAt(0).providers - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - RealmSelectionFragment.newInstance( - realmList = appMap!!.values.elementAt(0).realms, - showRealmTextInput = showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } - } + if (!appMap.isNullOrEmpty()) { + if (appMap!!.size == 1) { + parentActivity.app = appMap!!.keys.elementAt(0) + parentActivity.consoleProviders = appMap!!.values.elementAt(0).providers + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + RealmSelectionFragment.newInstance( + realmList = appMap!!.values.elementAt(0).realms, + showRealmTextInput = showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } } + } - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - // Inflate the layout for this fragment - binding = FragmentAppSelectionBinding.inflate(inflater, container, false); - val view: View = binding.root - - if (showAppTextInput) { - binding.appInputLayout.visibility = View.VISIBLE - binding.appInput.doOnTextChanged { text, start, before, count -> - parentActivity.app = text.toString().trim() - } - } else { - if (appList != null) { - appArrayAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_dropdown_item, - appList!! - ) - binding.appSpinner.adapter = appArrayAdapter - binding.appSpinner.visibility = View.VISIBLE - } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + // Inflate the layout for this fragment + binding = FragmentAppSelectionBinding.inflate(inflater, container, false) + val view: View = binding.root - if (appMap != null) { - appArrayAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_dropdown_item, - appMap!!.keys.toList() - ) - binding.appSpinner.adapter = appArrayAdapter - binding.appSpinner.visibility = View.VISIBLE - } + if (showAppTextInput) { + binding.appInputLayout.visibility = View.VISIBLE + binding.appInput.doOnTextChanged { text, start, before, count -> + parentActivity.app = text.toString().trim() + } + } else { + if (appList != null) { + appArrayAdapter = + ArrayAdapter( + requireActivity(), + android.R.layout.simple_spinner_dropdown_item, + appList!!, + ) + binding.appSpinner.adapter = appArrayAdapter + binding.appSpinner.visibility = View.VISIBLE + } - binding.appSpinner.onItemSelectedListener = - object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, - view: View?, - position: Int, - id: Long - ) { - parentActivity.app = appArrayAdapter.getItem(position) - parentActivity.consoleProviders = - appMap?.values?.elementAt(position)?.providers - } - - override fun onNothingSelected(p0: AdapterView<*>?) { - parentActivity.app = null - parentActivity.consoleProviders = null - } - } - } + if (appMap != null) { + appArrayAdapter = + ArrayAdapter( + requireActivity(), + android.R.layout.simple_spinner_dropdown_item, + appMap!!.keys.toList(), + ) + binding.appSpinner.adapter = appArrayAdapter + binding.appSpinner.visibility = View.VISIBLE + } + + binding.appSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long, + ) { + parentActivity.app = appArrayAdapter.getItem(position) + parentActivity.consoleProviders = appMap?.values?.elementAt(position)?.providers + } - binding.backButton.setOnClickListener { - parentFragmentManager.popBackStack() + override fun onNothingSelected(p0: AdapterView<*>?) { + parentActivity.app = null + parentActivity.consoleProviders = null + } } + } - binding.nextButton.setOnClickListener { - if (parentActivity.app != null) { - val appInfo = appMap?.get(parentActivity.app) - if (appInfo == null) { - parentActivity.apiManager.getAppInfos { statusCode, appInfoMap, error -> - parentActivity.binding.progressBar.visibility = View.INVISIBLE - when (statusCode) { - in 200..299 -> { - appInfoMap?.let { - val appInfo = it[parentActivity.app] - if (appInfo != null) { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - RealmSelectionFragment.newInstance( - realmList = appInfo.realms, - showRealmTextInput = showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } else { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - RealmSelectionFragment.newInstance( - realmList = null, - showRealmTextInput = showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } - } - } - 404 -> { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - RealmSelectionFragment.newInstance( - realmList = null, - showRealmTextInput = showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } - else -> { - parentActivity.runOnUiThread { - binding.errorView.visibility = View.VISIBLE - binding.errorTextView.text = - getString(R.string.error_getting_app_info) - } - } - } - } - } else { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - RealmSelectionFragment.newInstance( - realmList = appInfo.realms, - showRealmTextInput = showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() + binding.backButton.setOnClickListener { + parentFragmentManager.popBackStack() + } + + binding.nextButton.setOnClickListener { + if (parentActivity.app != null) { + val appInfo = appMap?.get(parentActivity.app) + if (appInfo == null) { + parentActivity.apiManager.getAppInfos { statusCode, appInfoMap, error -> + parentActivity.binding.progressBar.visibility = View.INVISIBLE + when (statusCode) { + in 200..299 -> { + appInfoMap?.let { + val appInfo = it[parentActivity.app] + if (appInfo != null) { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + RealmSelectionFragment.newInstance( + realmList = appInfo.realms, + showRealmTextInput = showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } else { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + RealmSelectionFragment.newInstance( + realmList = null, + showRealmTextInput = showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } } - } else { + } + 404 -> { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + RealmSelectionFragment.newInstance( + realmList = null, + showRealmTextInput = showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } + else -> { parentActivity.runOnUiThread { - binding.errorView.visibility = View.VISIBLE - binding.errorTextView.text = getString(R.string.select_app_first) + binding.errorView.visibility = View.VISIBLE + binding.errorTextView.text = getString(R.string.error_getting_app_info) } + } } + } + } else { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + RealmSelectionFragment.newInstance( + realmList = appInfo.realms, + showRealmTextInput = showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() } - - return view + } else { + parentActivity.runOnUiThread { + binding.errorView.visibility = View.VISIBLE + binding.errorTextView.text = getString(R.string.select_app_first) + } + } } - companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @return A new instance of fragment AppSelectionFragment. - */ - @JvmStatic - fun newInstance( - appList: List?, - appMap: Map?, - showAppTextInput: Boolean = false, - showRealmTextInput: Boolean = false - ) = - AppSelectionFragment().apply { - arguments = Bundle().apply { - appList?.let { - putString(ARG_APP_LIST, mapper.writeValueAsString(it)) - } - appMap?.let { - putString(ARG_APP_MAP, mapper.writeValueAsString(it)) - } - putBoolean(ARG_SHOW_REALM_TEXT_INPUT, showRealmTextInput) - putBoolean(ARG_SHOW_APP_TEXT_INPUT, showAppTextInput) - } + return view + } + + companion object { + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @return A new instance of fragment AppSelectionFragment. + */ + @JvmStatic + fun newInstance( + appList: List?, + appMap: Map?, + showAppTextInput: Boolean = false, + showRealmTextInput: Boolean = false, + ) = + AppSelectionFragment().apply { + arguments = + Bundle().apply { + appList?.let { + putString(ARG_APP_LIST, mapper.writeValueAsString(it)) } - } + appMap?.let { + putString(ARG_APP_MAP, mapper.writeValueAsString(it)) + } + putBoolean(ARG_SHOW_REALM_TEXT_INPUT, showRealmTextInput) + putBoolean(ARG_SHOW_APP_TEXT_INPUT, showAppTextInput) + } + } + } } diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt index 1331c73..4516f91 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/HostSelectionFragment.kt @@ -1,10 +1,27 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.ui import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.webkit.URLUtil import androidx.fragment.app.Fragment import io.openremote.app.R import io.openremote.app.databinding.FragmentHostSelectionBinding @@ -13,259 +30,270 @@ import io.openremote.app.model.ORConsoleConfig import io.openremote.app.network.ApiManager import io.openremote.app.util.UrlUtils - /** - * A simple [Fragment] subclass. - * Use the [HostSelectionFragment.newInstance] factory method to + * A simple [Fragment] subclass. Use the [HostSelectionFragment.newInstance] factory method to * create an instance of this fragment. */ class HostSelectionFragment : Fragment() { - private lateinit var binding: FragmentHostSelectionBinding - private lateinit var parentActivity: ProjectWizardActivity - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - - parentActivity = requireActivity() as ProjectWizardActivity + private lateinit var binding: FragmentHostSelectionBinding + private lateinit var parentActivity: ProjectWizardActivity - // Inflate the layout for this fragment - binding = FragmentHostSelectionBinding.inflate(inflater, container, false); - val view: View = binding.root + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + parentActivity = requireActivity() as ProjectWizardActivity - binding.connectButton.setOnClickListener { - if (!binding.hostInput.text.isNullOrBlank()) { - connectToHost(binding.hostInput.text.toString().trim()) - } - parentActivity.host = binding.hostInput.text.toString().trim() - } + // Inflate the layout for this fragment + binding = FragmentHostSelectionBinding.inflate(inflater, container, false) + val view: View = binding.root - binding.backButton.setOnClickListener { - activity?.finish() - } + binding.connectButton.setOnClickListener { + if (!binding.hostInput.text.isNullOrBlank()) { + connectToHost(binding.hostInput.text.toString().trim()) + } + parentActivity.host = binding.hostInput.text.toString().trim() + } - return view + binding.backButton.setOnClickListener { + activity?.finish() } - private fun connectToHost(host: String) { - parentActivity.binding.progressBar.visibility = View.VISIBLE - val url = UrlUtils.hostToUrl(host).plus("/api/master") - parentActivity.apiManager = ApiManager(url) - parentActivity.apiManager.getConsoleConfig { statusCode, consoleConfig, error -> - when (statusCode) { - in 200..299 -> { - consoleConfig?.let { config -> - if (config.app != null) { - parentActivity.app = config.app - val appInfo = config.apps?.get(config.app) - if (appInfo != null) { - parentActivity.binding.progressBar.visibility = View.INVISIBLE - processAppInfo(config, appInfo) - } else { - parentActivity.apiManager.getAppInfos { statusCode, appInfoMap, error -> - parentActivity.binding.progressBar.visibility = View.INVISIBLE - when (statusCode) { - in 200..299 -> { - if (appInfoMap != null) { - val appInfo = appInfoMap[config.app!!] - if (appInfo != null) { - processAppInfo(config, appInfo) - } else { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = config.allowedApps, - appMap = config.apps, - showAppTextInput = config.showAppTextInput, - showRealmTextInput = config.showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } - } else { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = config.allowedApps, - appMap = config.apps, - showAppTextInput = config.showAppTextInput, - showRealmTextInput = config.showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } - } - 404 -> { //no appconfig - parentActivity.goToMainActivity( - parentActivity.app!!, - "master" - ) - } - else -> { - binding.errorView.visibility = View.VISIBLE - parentActivity.runOnUiThread { - binding.errorView.visibility = View.VISIBLE - binding.errorTextView.text = - resources.getText(R.string.error_getting_app_info) - } - } - } - } - } + return view + } + + private fun connectToHost(host: String) { + parentActivity.binding.progressBar.visibility = View.VISIBLE + val url = UrlUtils.hostToUrl(host).plus("/api/master") + parentActivity.apiManager = ApiManager(url) + parentActivity.apiManager.getConsoleConfig { statusCode, consoleConfig, error -> + when (statusCode) { + in 200..299 -> { + consoleConfig?.let { config -> + if (config.app != null) { + parentActivity.app = config.app + val appInfo = config.apps?.get(config.app) + if (appInfo != null) { + parentActivity.binding.progressBar.visibility = View.INVISIBLE + processAppInfo(config, appInfo) + } else { + parentActivity.apiManager.getAppInfos { statusCode, appInfoMap, error -> + parentActivity.binding.progressBar.visibility = View.INVISIBLE + when (statusCode) { + in 200..299 -> { + if (appInfoMap != null) { + val appInfo = appInfoMap[config.app!!] + if (appInfo != null) { + processAppInfo(config, appInfo) } else { - parentActivity.binding.progressBar.visibility = View.INVISIBLE - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = config.allowedApps, - appMap = config.apps, - showAppTextInput = config.showAppTextInput, - showRealmTextInput = config.showRealmTextInput - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = config.allowedApps, + appMap = config.apps, + showAppTextInput = config.showAppTextInput, + showRealmTextInput = config.showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() } + } else { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = config.allowedApps, + appMap = config.apps, + showAppTextInput = config.showAppTextInput, + showRealmTextInput = config.showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } } - } - 404 -> { //no console_config.json - parentActivity.apiManager.getAppInfos { statusCode, appInfoMap, error -> - parentActivity.binding.progressBar.visibility = View.INVISIBLE - when (statusCode) { - in 200..299 -> { - if (appInfoMap?.isNotEmpty() == true) { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = null, - appMap = appInfoMap - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } else { - parentActivity.apiManager.getApps { statusCode, apps, error -> - if (apps?.isNotEmpty() == true) { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = apps, - appMap = null - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } else { - //TODO remove inserting manager app after all manager instances are updated with console_settings_improvement branch - val tempApps = arrayListOf("manager") - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = tempApps, - appMap = null, - showAppTextInput = true - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - -// parentActivity.runOnUiThread { -// binding.errorView.visibility = View.VISIBLE -// binding.errorTextView.text = -// getString(R.string.no_apps_found) -// } - } - } - } - } - else -> { - parentActivity.apiManager.getApps { statusCode, apps, error -> - if (apps?.isNotEmpty() == true) { - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = apps, - appMap = null, - showAppTextInput = true - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() - } else { - //TODO remove inserting manager app after all manager instances are updated with console_settings_improvement branch - val tempApps = arrayListOf("manager") - parentFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, - AppSelectionFragment.newInstance( - appList = tempApps, - appMap = null - ) - ) - .addToBackStack(ProjectWizardActivity.TAG) - .commit() -// parentActivity.runOnUiThread { -// binding.errorView.visibility = View.VISIBLE -// binding.errorTextView.text = -// getString(R.string.no_apps_found) -// } - } - } - } - } + 404 -> { // no appconfig + parentActivity.goToMainActivity( + parentActivity.app!!, + "master", + ) } - } - else -> { - parentActivity.binding.progressBar.visibility = View.INVISIBLE - parentActivity.runOnUiThread { + else -> { + binding.errorView.visibility = View.VISIBLE + parentActivity.runOnUiThread { binding.errorView.visibility = View.VISIBLE binding.errorTextView.text = - resources.getText(R.string.could_not_connect_to_domain) + resources.getText(R.string.error_getting_app_info) + } } + } } - } - } - } - - private fun processAppInfo(config: ORConsoleConfig, appInfo: ORAppInfo) { - parentActivity.consoleProviders = appInfo.providers - if (appInfo.consoleAppIncompatible) { - parentActivity.runOnUiThread { - binding.errorView.visibility = View.VISIBLE - binding.errorTextView.text = getString(R.string.app_incompatible) - } - } else if (parentActivity.app != null) { - parentActivity.goToMainActivity( - parentActivity.app!!, - consoleProviders = appInfo.providers - ) - } else { - parentFragmentManager.beginTransaction() + } + } else { + parentActivity.binding.progressBar.visibility = View.INVISIBLE + parentFragmentManager + .beginTransaction() .replace( - R.id.fragmentContainer, - RealmSelectionFragment.newInstance( - realmList = appInfo.realms, - showRealmTextInput = config.showRealmTextInput - ) + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = config.allowedApps, + appMap = config.apps, + showAppTextInput = config.showAppTextInput, + showRealmTextInput = config.showRealmTextInput, + ), ) .addToBackStack(ProjectWizardActivity.TAG) .commit() + } + } } + 404 -> { // no console_config.json + parentActivity.apiManager.getAppInfos { statusCode, appInfoMap, error -> + parentActivity.binding.progressBar.visibility = View.INVISIBLE + when (statusCode) { + in 200..299 -> { + if (appInfoMap?.isNotEmpty() == true) { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = null, + appMap = appInfoMap, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } else { + parentActivity.apiManager.getApps { statusCode, apps, error -> + if (apps?.isNotEmpty() == true) { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = apps, + appMap = null, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } else { + // TODO remove inserting manager app after all manager instances are updated + // with console_settings_improvement branch + val tempApps = arrayListOf("manager") + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = tempApps, + appMap = null, + showAppTextInput = true, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + + // parentActivity.runOnUiThread { + // binding.errorView.visibility + // = View.VISIBLE + // binding.errorTextView.text = + // + // getString(R.string.no_apps_found) + // } + } + } + } + } + else -> { + parentActivity.apiManager.getApps { statusCode, apps, error -> + if (apps?.isNotEmpty() == true) { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = apps, + appMap = null, + showAppTextInput = true, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + } else { + // TODO remove inserting manager app after all manager instances are updated + // with console_settings_improvement branch + val tempApps = arrayListOf("manager") + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + AppSelectionFragment.newInstance( + appList = tempApps, + appMap = null, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() + // parentActivity.runOnUiThread { + // binding.errorView.visibility = + // View.VISIBLE + // binding.errorTextView.text = + // + // getString(R.string.no_apps_found) + // } + } + } + } + } + } + } + else -> { + parentActivity.binding.progressBar.visibility = View.INVISIBLE + parentActivity.runOnUiThread { + binding.errorView.visibility = View.VISIBLE + binding.errorTextView.text = resources.getText(R.string.could_not_connect_to_domain) + } + } + } } + } - companion object { - @JvmStatic - fun newInstance() = HostSelectionFragment() + private fun processAppInfo(config: ORConsoleConfig, appInfo: ORAppInfo) { + parentActivity.consoleProviders = appInfo.providers + if (appInfo.consoleAppIncompatible) { + parentActivity.runOnUiThread { + binding.errorView.visibility = View.VISIBLE + binding.errorTextView.text = getString(R.string.app_incompatible) + } + } else if (parentActivity.app != null) { + parentActivity.goToMainActivity( + parentActivity.app!!, + consoleProviders = appInfo.providers, + ) + } else { + parentFragmentManager + .beginTransaction() + .replace( + R.id.fragmentContainer, + RealmSelectionFragment.newInstance( + realmList = appInfo.realms, + showRealmTextInput = config.showRealmTextInput, + ), + ) + .addToBackStack(ProjectWizardActivity.TAG) + .commit() } + } + + companion object { + @JvmStatic fun newInstance() = HostSelectionFragment() + } } diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListActivity.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListActivity.kt index b61509b..43078e5 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListActivity.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListActivity.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.ui import android.content.Intent @@ -25,108 +43,127 @@ import io.openremote.app.util.Constants import io.openremote.orlib.ORConstants import io.openremote.orlib.ui.OrMainActivity - class ProjectListActivity : AppCompatActivity() { - lateinit var binding: ActivityProjectListBinding - lateinit var projectListAdapter: ProjectListAdapter + lateinit var binding: ActivityProjectListBinding + lateinit var projectListAdapter: ProjectListAdapter - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - binding = ActivityProjectListBinding.inflate(layoutInflater) - val view = binding.root - setContentView(view) + binding = ActivityProjectListBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) - val toolbar: Toolbar = findViewById(R.id.settings_toolbar); - setSupportActionBar(toolbar) - supportActionBar?.setDisplayShowTitleEnabled(false) + val toolbar: Toolbar = findViewById(R.id.settings_toolbar) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayShowTitleEnabled(false) - val projectItems = PreferenceManager.getDefaultSharedPreferences(this) - .getString(Constants.PROJECT_LIST, null)?.let { - jacksonObjectMapper().readValue>(it).toMutableList() + val projectItems = + PreferenceManager.getDefaultSharedPreferences(this) + .getString(Constants.PROJECT_LIST, null) + ?.let { + jacksonObjectMapper().readValue>(it).toMutableList() } ?: mutableListOf() - projectListAdapter = ProjectListAdapter(projectItems.toMutableList().asReversed(), this::goToMainActivity) - binding.projectList.adapter = projectListAdapter - binding.projectList.layoutManager = LinearLayoutManager(this); - - val background = ColorDrawable(Color.parseColor("#bf1515")) - val xMark = ContextCompat.getDrawable(this, android.R.drawable.ic_menu_close_clear_cancel)?.apply { - setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) + projectListAdapter = + ProjectListAdapter(projectItems.toMutableList().asReversed(), this::goToMainActivity) + binding.projectList.adapter = projectListAdapter + binding.projectList.layoutManager = LinearLayoutManager(this) + + val background = ColorDrawable(Color.parseColor("#bf1515")) + val xMark = + ContextCompat.getDrawable(this, android.R.drawable.ic_menu_close_clear_cancel)?.apply { + setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) + } + val xMarkMargin = 24 + + val swipeHandler = + object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder, + ): Boolean { + return false } - val xMarkMargin = 24 - - val swipeHandler = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { - - override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { - return false - } - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - val projectItems = projectListAdapter.items.toMutableList().asReversed() - projectItems.removeAt(viewHolder.adapterPosition) - PreferenceManager.getDefaultSharedPreferences(this@ProjectListActivity) - .edit() - .putString(Constants.PROJECT_LIST, jacksonObjectMapper().writeValueAsString(projectItems)) - .apply() - projectListAdapter.remove(viewHolder.adapterPosition) - } - - override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { - if (viewHolder.adapterPosition < 0) return - - val itemView = viewHolder.itemView // the view being swiped - - // draw the red background, based on the offset of the swipe (dX) - background.apply { - setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom) - draw(c) - } - - // draw the symbol - xMark?.apply { - val xt = itemView.top + (itemView.bottom - itemView.top - xMark.intrinsicHeight) / 2 - setBounds( - itemView.right - xMarkMargin - xMark.intrinsicWidth, - xt, - itemView.right - xMarkMargin, - xt + xMark.intrinsicHeight - ) - draw(c) - } - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) - } - } - val touchHelper = ItemTouchHelper(swipeHandler) - touchHelper.attachToRecyclerView(binding.projectList) - } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.projects_menu, menu) - val menuItem = menu?.findItem(R.id.add); - menuItem?.actionView?.setOnClickListener { - this.onOptionsItemSelected(menuItem) + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val projectItems = projectListAdapter.items.toMutableList().asReversed() + projectItems.removeAt(viewHolder.adapterPosition) + PreferenceManager.getDefaultSharedPreferences(this@ProjectListActivity) + .edit() + .putString( + Constants.PROJECT_LIST, + jacksonObjectMapper().writeValueAsString(projectItems), + ) + .apply() + projectListAdapter.remove(viewHolder.adapterPosition) } - menuItem?.actionView?.setPadding(10) - return super.onCreateOptionsMenu(menu) - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId){ - R.id.add -> { - val intent = Intent(this, ProjectWizardActivity::class.java) - startActivity(intent) - } + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean, + ) { + if (viewHolder.adapterPosition < 0) return + + val itemView = viewHolder.itemView // the view being swiped + + // draw the red background, based on the offset of the swipe (dX) + background.apply { + setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom) + draw(c) + } + + // draw the symbol + xMark?.apply { + val xt = itemView.top + (itemView.bottom - itemView.top - xMark.intrinsicHeight) / 2 + setBounds( + itemView.right - xMarkMargin - xMark.intrinsicWidth, + xt, + itemView.right - xMarkMargin, + xt + xMark.intrinsicHeight, + ) + draw(c) + } + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } - return super.onOptionsItemSelected(item) + } + val touchHelper = ItemTouchHelper(swipeHandler) + touchHelper.attachToRecyclerView(binding.projectList) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.projects_menu, menu) + val menuItem = menu?.findItem(R.id.add) + menuItem?.actionView?.setOnClickListener { + this.onOptionsItemSelected(menuItem) } - - private fun goToMainActivity(url: String) { - val intent = Intent(this, OrMainActivity::class.java) - intent.putExtra(ORConstants.BASE_URL_KEY, url) - runOnUiThread { - startActivity(intent) - finish() - } + menuItem?.actionView?.setPadding(10) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.add -> { + val intent = Intent(this, ProjectWizardActivity::class.java) + startActivity(intent) + } + } + return super.onOptionsItemSelected(item) + } + + private fun goToMainActivity(url: String) { + val intent = Intent(this, OrMainActivity::class.java) + intent.putExtra(ORConstants.BASE_URL_KEY, url) + runOnUiThread { + startActivity(intent) + finish() } + } } diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListAdapter.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListAdapter.kt index bb7c7eb..6bcc336 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListAdapter.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectListAdapter.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.ui import android.view.LayoutInflater @@ -7,55 +25,60 @@ import androidx.recyclerview.widget.RecyclerView import io.openremote.app.databinding.RowProjectItemBinding import io.openremote.app.model.ProjectItem -class ProjectListAdapter(val items: MutableList, private val goToMainActivity: (url: String) -> (Unit)) : RecyclerView.Adapter() { +class ProjectListAdapter( + val items: MutableList, + private val goToMainActivity: (url: String) -> (Unit), +) : RecyclerView.Adapter() { - // create new views - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val inflater = LayoutInflater.from(parent.context) - val binding = RowProjectItemBinding.inflate(inflater, parent, false) - return ViewHolder(binding, goToMainActivity) - } + // create new views + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = RowProjectItemBinding.inflate(inflater, parent, false) + return ViewHolder(binding, goToMainActivity) + } - // binds the list items to a view - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.bindProjectItem(items[position]) - } + // binds the list items to a view + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bindProjectItem(items[position]) + } + + // return the number of the items in the list + override fun getItemCount(): Int { + return items.size + } + + fun remove(position: Int) { + this.items.removeAt(position) + this.notifyDataSetChanged() + } - // return the number of the items in the list - override fun getItemCount(): Int { - return items.size + // Holds the views for adding it to image and text + class ViewHolder( + private val binding: RowProjectItemBinding, + private val goToMainActivity: (url: String) -> (Unit), + ) : RecyclerView.ViewHolder(binding.root), View.OnClickListener { + + lateinit var url: String + + init { + binding.root.setOnClickListener(this) } - fun remove(position: Int) { - this.items.removeAt(position) - this.notifyDataSetChanged() + fun bindProjectItem(projectItem: ProjectItem) { + this.url = projectItem.url + binding.host.text = projectItem.host + projectItem.app.run { + binding.app.visibility = View.VISIBLE + binding.app.text = this + } + projectItem.realm?.run { + binding.realm.visibility = View.VISIBLE + binding.realm.text = this + } } - // Holds the views for adding it to image and text - class ViewHolder(private val binding: RowProjectItemBinding, private val goToMainActivity: (url: String) -> (Unit)) : RecyclerView.ViewHolder(binding.root), View.OnClickListener { - - lateinit var url: String - - init { - binding.root.setOnClickListener(this) - } - - fun bindProjectItem(projectItem: ProjectItem) { - this.url = projectItem.url - binding.host.text = projectItem.host - projectItem.app.run { - binding.app.visibility = View.VISIBLE - binding.app.text = this - } - projectItem.realm?.run { - binding.realm.visibility = View.VISIBLE - binding.realm.text = this - } - } - - override fun onClick(view: View?) { - goToMainActivity(url) - } + override fun onClick(view: View?) { + goToMainActivity(url) } + } } - diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectWizardActivity.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectWizardActivity.kt index 2839754..25f237e 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectWizardActivity.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/ProjectWizardActivity.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.ui import android.content.Intent @@ -17,68 +35,70 @@ import io.openremote.orlib.ui.OrMainActivity class ProjectWizardActivity : FragmentActivity() { - lateinit var binding: ActivityProjectWizardBinding - lateinit var apiManager: ApiManager + lateinit var binding: ActivityProjectWizardBinding + lateinit var apiManager: ApiManager - var host: String? = null - var app: String? = null - var realm: String? = null - var consoleProviders: List? = null + var host: String? = null + var app: String? = null + var realm: String? = null + var consoleProviders: List? = null - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - binding = ActivityProjectWizardBinding.inflate(layoutInflater) - val view = binding.root - setContentView(view) + binding = ActivityProjectWizardBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) - supportFragmentManager.beginTransaction() - .add(R.id.fragmentContainer, HostSelectionFragment.newInstance()) - .addToBackStack(TAG) - .commit() - } + supportFragmentManager + .beginTransaction() + .add(R.id.fragmentContainer, HostSelectionFragment.newInstance()) + .addToBackStack(TAG) + .commit() + } - fun goToMainActivity( - appName: String = "manager", - realm: String? = null, - consoleProviders: List? = null - ) { - val preferences = PreferenceManager.getDefaultSharedPreferences(this) - val projectItems = preferences.getString(Constants.PROJECT_LIST, null)?.let { - jacksonObjectMapper().readValue>(it).toMutableList() - } ?: mutableListOf() + fun goToMainActivity( + appName: String = "manager", + realm: String? = null, + consoleProviders: List? = null, + ) { + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val projectItems = + preferences.getString(Constants.PROJECT_LIST, null)?.let { + jacksonObjectMapper().readValue>(it).toMutableList() + } ?: mutableListOf() - val intent = Intent(this, OrMainActivity::class.java) - var url = if (URLUtil.isValidUrl(host)) host else "https://${host}.openremote.app" - url = if (realm != null) "$url/${appName}/?realm=$realm" else "$url/$appName" - if (consoleProviders?.any() == true) { - url = if (url.contains("?")) url.plus("&") else url.plus("?") - url = url.plus("consoleProviders=${consoleProviders.joinToString(" ")}") - } + val intent = Intent(this, OrMainActivity::class.java) + var url = if (URLUtil.isValidUrl(host)) host else "https://${host}.openremote.app" + url = if (realm != null) "$url/${appName}/?realm=$realm" else "$url/$appName" + if (consoleProviders?.any() == true) { + url = if (url.contains("?")) url.plus("&") else url.plus("?") + url = url.plus("consoleProviders=${consoleProviders.joinToString(" ")}") + } - projectItems.add(ProjectItem(host!!, appName, realm, url)) - preferences - .edit() - .putString(Constants.PROJECT_LIST, jacksonObjectMapper().writeValueAsString(projectItems)) - .apply() + projectItems.add(ProjectItem(host!!, appName, realm, url)) + preferences + .edit() + .putString(Constants.PROJECT_LIST, jacksonObjectMapper().writeValueAsString(projectItems)) + .apply() - intent.putExtra(ORConstants.BASE_URL_KEY, url) - runOnUiThread { - startActivity(intent) - finish() - } + intent.putExtra(ORConstants.BASE_URL_KEY, url) + runOnUiThread { + startActivity(intent) + finish() } + } - companion object { - const val TAG = "ProjectWizardActivity" - } + companion object { + const val TAG = "ProjectWizardActivity" + } - override fun onBackPressed() { - val f = supportFragmentManager.findFragmentById(R.id.fragmentContainer) - if (f is HostSelectionFragment) { - finish() - } else { - super.onBackPressed() - } + override fun onBackPressed() { + val f = supportFragmentManager.findFragmentById(R.id.fragmentContainer) + if (f is HostSelectionFragment) { + finish() + } else { + super.onBackPressed() } + } } diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/RealmSelectionFragment.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/RealmSelectionFragment.kt index a65cdbb..4be719b 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/RealmSelectionFragment.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/RealmSelectionFragment.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.ui import android.os.Bundle @@ -16,132 +34,132 @@ import io.openremote.app.databinding.FragmentRealmSelectionBinding private const val ARG_REALM_LIST = "realmList" private const val ARG_SHOW_REALM_TEXT_INPUT = "showRealmTextInput" - /** - * A simple [Fragment] subclass. - * Use the [RealmSelectionFragment.newInstance] factory method to + * A simple [Fragment] subclass. Use the [RealmSelectionFragment.newInstance] factory method to * create an instance of this fragment. */ class RealmSelectionFragment : Fragment() { - private lateinit var binding: FragmentRealmSelectionBinding - private lateinit var parentActivity: ProjectWizardActivity + private lateinit var binding: FragmentRealmSelectionBinding + private lateinit var parentActivity: ProjectWizardActivity - lateinit var realmArrayAdapter: ArrayAdapter + lateinit var realmArrayAdapter: ArrayAdapter - private var realmList: List? = null - private var showRealmTextInput: Boolean = false + private var realmList: List? = null + private var showRealmTextInput: Boolean = false - private val mapper = jacksonObjectMapper() + private val mapper = jacksonObjectMapper() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + parentActivity = requireActivity() as ProjectWizardActivity - parentActivity = requireActivity() as ProjectWizardActivity - - arguments?.let { - it.getString(ARG_REALM_LIST)?.let { - realmList = mapper.readValue>(it) - } - showRealmTextInput = it.getBoolean(ARG_SHOW_REALM_TEXT_INPUT, false) - } - - if (!realmList.isNullOrEmpty()) { - if (realmList!!.size == 1) { - parentActivity.realm = realmList!![0] - parentActivity.goToMainActivity( - parentActivity.app!!, - parentActivity.realm, - parentActivity.consoleProviders - ) - } - } + arguments?.let { + it.getString(ARG_REALM_LIST)?.let { + realmList = mapper.readValue>(it) + } + showRealmTextInput = it.getBoolean(ARG_SHOW_REALM_TEXT_INPUT, false) } - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - // Inflate the layout for this fragment - binding = FragmentRealmSelectionBinding.inflate(inflater, container, false); - val view: View = binding.root - - if (showRealmTextInput) { - binding.realmInputLayout.visibility = View.VISIBLE - binding.realmInput.doOnTextChanged { text, start, before, count -> - parentActivity.realm = text.toString().trim() - } - } else { - if (realmList != null) { - realmArrayAdapter = ArrayAdapter( - requireActivity(), - android.R.layout.simple_spinner_dropdown_item, - realmList!! - ) - binding.realmSpinner.adapter = realmArrayAdapter - binding.realmSpinner.visibility = View.VISIBLE - - binding.realmSpinner.onItemSelectedListener = - object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, - view: View?, - position: Int, - id: Long - ) { - parentActivity.realm = realmArrayAdapter.getItem(position) - } - - override fun onNothingSelected(p0: AdapterView<*>?) { - parentActivity.realm = null - } - } - } else { - binding.realmInputLayout.visibility = View.VISIBLE - binding.realmInput.doOnTextChanged { text, start, before, count -> - parentActivity.realm = text.toString().trim() - } + if (!realmList.isNullOrEmpty()) { + if (realmList!!.size == 1) { + parentActivity.realm = realmList!![0] + parentActivity.goToMainActivity( + parentActivity.app!!, + parentActivity.realm, + parentActivity.consoleProviders, + ) + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + // Inflate the layout for this fragment + binding = FragmentRealmSelectionBinding.inflate(inflater, container, false) + val view: View = binding.root + + if (showRealmTextInput) { + binding.realmInputLayout.visibility = View.VISIBLE + binding.realmInput.doOnTextChanged { text, start, before, count -> + parentActivity.realm = text.toString().trim() + } + } else { + if (realmList != null) { + realmArrayAdapter = + ArrayAdapter( + requireActivity(), + android.R.layout.simple_spinner_dropdown_item, + realmList!!, + ) + binding.realmSpinner.adapter = realmArrayAdapter + binding.realmSpinner.visibility = View.VISIBLE + + binding.realmSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long, + ) { + parentActivity.realm = realmArrayAdapter.getItem(position) } - } - binding.finishButton.setOnClickListener { - if (parentActivity.realm != null) { - parentActivity.goToMainActivity( - parentActivity.app!!, - parentActivity.realm, - parentActivity.consoleProviders - ) - } else { - parentActivity.runOnUiThread { - binding.errorView.visibility = View.VISIBLE - binding.errorTextView.text = getString(R.string.select_realm_first) - } + override fun onNothingSelected(p0: AdapterView<*>?) { + parentActivity.realm = null } + } + } else { + binding.realmInputLayout.visibility = View.VISIBLE + binding.realmInput.doOnTextChanged { text, start, before, count -> + parentActivity.realm = text.toString().trim() } + } + } - binding.backButton.setOnClickListener { - parentFragmentManager.popBackStack() + binding.finishButton.setOnClickListener { + if (parentActivity.realm != null) { + parentActivity.goToMainActivity( + parentActivity.app!!, + parentActivity.realm, + parentActivity.consoleProviders, + ) + } else { + parentActivity.runOnUiThread { + binding.errorView.visibility = View.VISIBLE + binding.errorTextView.text = getString(R.string.select_realm_first) } + } + } - return view + binding.backButton.setOnClickListener { + parentFragmentManager.popBackStack() } - companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @return A new instance of fragment RealmSelectionFragment. - */ - @JvmStatic - fun newInstance(realmList: List?, showRealmTextInput: Boolean = false) = - RealmSelectionFragment().apply { - arguments = Bundle().apply { - realmList?.let { - putString(ARG_REALM_LIST, mapper.writeValueAsString(it)) - } - putBoolean(ARG_SHOW_REALM_TEXT_INPUT, showRealmTextInput) - } + return view + } + + companion object { + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @return A new instance of fragment RealmSelectionFragment. + */ + @JvmStatic + fun newInstance(realmList: List?, showRealmTextInput: Boolean = false) = + RealmSelectionFragment().apply { + arguments = + Bundle().apply { + realmList?.let { + putString(ARG_REALM_LIST, mapper.writeValueAsString(it)) } - } + putBoolean(ARG_SHOW_REALM_TEXT_INPUT, showRealmTextInput) + } + } + } } diff --git a/GenericApp/app/src/main/java/io/openremote/app/ui/SplashActivity.kt b/GenericApp/app/src/main/java/io/openremote/app/ui/SplashActivity.kt index 860fbff..e5f7000 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/ui/SplashActivity.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/ui/SplashActivity.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.ui import android.annotation.SuppressLint @@ -18,73 +36,72 @@ import io.openremote.orlib.ui.OrPrivacyPolicyActivity @SuppressLint("CustomSplashScreen") class SplashActivity : Activity() { - object SplashActivityConstants { - const val SHOW_CONDITIONS_REQUEST = 34563 - } + object SplashActivityConstants { + const val SHOW_CONDITIONS_REQUEST = 34563 + } - private lateinit var binding: ActivitySplashBinding - private lateinit var sharedPreferences: SharedPreferences - private lateinit var projectItems: MutableList + private lateinit var binding: ActivitySplashBinding + private lateinit var sharedPreferences: SharedPreferences + private lateinit var projectItems: MutableList - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivitySplashBinding.inflate(layoutInflater) - val view = binding.root - setContentView(view) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivitySplashBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) - projectItems = sharedPreferences.getString(Constants.PROJECT_LIST, null)?.let { - jacksonObjectMapper().readValue>(it).toMutableList() - } ?: mutableListOf() + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + projectItems = + sharedPreferences.getString(Constants.PROJECT_LIST, null)?.let { + jacksonObjectMapper().readValue>(it).toMutableList() + } ?: mutableListOf() - intent.extras?.apply { - this.getString(ORConstants.CLEAR_URL)?.apply { - projectItems.removeIf { it.url == this } - sharedPreferences - .edit() - .putString(Constants.PROJECT_LIST, jacksonObjectMapper().writeValueAsString(projectItems)) - .apply() - } - } + intent.extras?.apply { + this.getString(ORConstants.CLEAR_URL)?.apply { + projectItems.removeIf { it.url == this } + sharedPreferences + .edit() + .putString(Constants.PROJECT_LIST, jacksonObjectMapper().writeValueAsString(projectItems)) + .apply() + } + } - val privacyPolicyAccepted = sharedPreferences.getBoolean("privacyPolicyAccepted", false) + val privacyPolicyAccepted = sharedPreferences.getBoolean("privacyPolicyAccepted", false) - if (!privacyPolicyAccepted) { - val intent = Intent(this@SplashActivity, OrPrivacyPolicyActivity::class.java) - startActivityForResult(intent, SplashActivityConstants.SHOW_CONDITIONS_REQUEST) - } else { - startNextActivity() - } + if (!privacyPolicyAccepted) { + val intent = Intent(this@SplashActivity, OrPrivacyPolicyActivity::class.java) + startActivityForResult(intent, SplashActivityConstants.SHOW_CONDITIONS_REQUEST) + } else { + startNextActivity() } + } - private fun startNextActivity() { - when { - projectItems.size == 1 -> { - val intent = Intent(this, OrMainActivity::class.java) - intent.putExtra(ORConstants.BASE_URL_KEY, projectItems[0].url) - startActivity(intent) - finish() - } - projectItems.size > 1 -> { - startActivity(Intent(this@SplashActivity, ProjectListActivity::class.java)) - finish() - } - else -> { - startActivity(Intent(this@SplashActivity, ProjectWizardActivity::class.java)) - finish() - } - } + private fun startNextActivity() { + when { + projectItems.size == 1 -> { + val intent = Intent(this, OrMainActivity::class.java) + intent.putExtra(ORConstants.BASE_URL_KEY, projectItems[0].url) + startActivity(intent) + finish() + } + projectItems.size > 1 -> { + startActivity(Intent(this@SplashActivity, ProjectListActivity::class.java)) + finish() + } + else -> { + startActivity(Intent(this@SplashActivity, ProjectWizardActivity::class.java)) + finish() + } } + } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == SplashActivityConstants.SHOW_CONDITIONS_REQUEST) { - if (resultCode == RESULT_OK) { - sharedPreferences.edit() - .putBoolean("privacyPolicyAccepted", true) - .apply() - startNextActivity() - } - } - super.onActivityResult(requestCode, resultCode, data) + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == SplashActivityConstants.SHOW_CONDITIONS_REQUEST) { + if (resultCode == RESULT_OK) { + sharedPreferences.edit().putBoolean("privacyPolicyAccepted", true).apply() + startNextActivity() + } } + super.onActivityResult(requestCode, resultCode, data) + } } diff --git a/GenericApp/app/src/main/java/io/openremote/app/util/Constants.kt b/GenericApp/app/src/main/java/io/openremote/app/util/Constants.kt index bb302ee..3791793 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/util/Constants.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/util/Constants.kt @@ -1,5 +1,23 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.util object Constants { - const val PROJECT_LIST = "PROJECT_LIST" + const val PROJECT_LIST = "PROJECT_LIST" } diff --git a/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt b/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt index 86fac3c..9159ed0 100644 --- a/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt +++ b/GenericApp/app/src/main/java/io/openremote/app/util/UrlUtils.kt @@ -1,25 +1,44 @@ +/* + * Copyright 2026, OpenRemote Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ package io.openremote.app.util object UrlUtils { - fun isIpV6NoScheme(url: String): Boolean { - val ipv6Pattern = Regex( - "^(?:([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6}))$" - ) - return ipv6Pattern.matches(url) - } + fun isIpV6NoScheme(url: String): Boolean { + val ipv6Pattern = + Regex( + "^(?:([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6}))$" + ) + return ipv6Pattern.matches(url) + } - fun startsWithScheme(url: String): Boolean { - val schemePattern = Regex("^[a-zA-Z]+://.*$") - return schemePattern.matches(url) - } + fun startsWithScheme(url: String): Boolean { + val schemePattern = Regex("^[a-zA-Z]+://.*$") + return schemePattern.matches(url) + } - fun hostToUrl(host: String): String { - return when { - isIpV6NoScheme(host) -> "https://[${host}]" - startsWithScheme(host) -> - if (host.contains(".") || host.contains("[")) host else "${host}.openremote.app" - (host.contains(".") || host.contains("[")) -> "https://${host}" - else -> "https://${host}.openremote.app" - } + fun hostToUrl(host: String): String { + return when { + isIpV6NoScheme(host) -> "https://[${host}]" + startsWithScheme(host) -> + if (host.contains(".") || host.contains("[")) host else "${host}.openremote.app" + (host.contains(".") || host.contains("[")) -> "https://${host}" + else -> "https://${host}.openremote.app" } -} \ No newline at end of file + } +} diff --git a/GenericApp/app/src/main/res/drawable/action_item_border.xml b/GenericApp/app/src/main/res/drawable/action_item_border.xml index 55cb934..4c3ca2f 100644 --- a/GenericApp/app/src/main/res/drawable/action_item_border.xml +++ b/GenericApp/app/src/main/res/drawable/action_item_border.xml @@ -1,6 +1,6 @@ - - - + + + diff --git a/GenericApp/app/src/main/res/drawable/app_icon.xml b/GenericApp/app/src/main/res/drawable/app_icon.xml index 37d07f4..97e7cbf 100644 --- a/GenericApp/app/src/main/res/drawable/app_icon.xml +++ b/GenericApp/app/src/main/res/drawable/app_icon.xml @@ -1,4 +1,3 @@ - - \ No newline at end of file + + diff --git a/GenericApp/app/src/main/res/drawable/ic_alert_circle.xml b/GenericApp/app/src/main/res/drawable/ic_alert_circle.xml index 36f5968..4f67dd7 100644 --- a/GenericApp/app/src/main/res/drawable/ic_alert_circle.xml +++ b/GenericApp/app/src/main/res/drawable/ic_alert_circle.xml @@ -1,10 +1,6 @@ - + + android:pathData="M13,13L11,13L11,7L13,7M13,17L11,17L11,15L13,15M12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2Z" + android:fillColor="#a07171" android:strokeColor="#00000000" /> diff --git a/GenericApp/app/src/main/res/drawable/ic_launcher_background.xml b/GenericApp/app/src/main/res/drawable/ic_launcher_background.xml index 07d5da9..cc8a48d 100644 --- a/GenericApp/app/src/main/res/drawable/ic_launcher_background.xml +++ b/GenericApp/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,69 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GenericApp/app/src/main/res/drawable/ic_launcher_foreground.xml b/GenericApp/app/src/main/res/drawable/ic_launcher_foreground.xml index 38953c9..5298cb1 100644 --- a/GenericApp/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/GenericApp/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,20 +1,15 @@ - - + + + android:pathData="M255.99,485.99c-126.81,0 -229.99,-103.16 -229.99,-229.99h48.17c0,100.27 81.57,181.84 181.83,181.84s181.84,-81.57 181.84,-181.84S356.26,74.17 255.99,74.17V26.01c126.82,0 230.01,103.18 230.01,229.99s-103.18,229.99 -230.01,229.99h0Z" + android:fillColor="#c2d500" /> + android:pathData="M221.17,422.85c-44.55,-9.28 -82.85,-35.39 -107.79,-73.47 -24.94,-38.08 -33.56,-83.63 -24.25,-128.19 9.31,-44.58 35.41,-82.85 73.49,-107.79 38.1,-24.95 83.61,-33.57 128.2,-24.25 92.02,19.2 151.26,109.66 132.04,201.65l-47.14,-9.81c13.76,-66.02 -28.73,-130.92 -94.76,-144.7 -31.99,-6.68 -64.64,-0.5 -91.96,17.41 -27.33,17.88 -46.05,45.35 -52.74,77.34 -6.65,31.97 -0.49,64.63 17.42,91.96 17.88,27.33 45.34,46.06 77.34,52.72l-9.86,47.14h0Z" + android:fillColor="#4c9c2e" /> + android:pathData="M256.14,366.5c-1.86,0 -3.72,-0.04 -5.61,-0.12 -29.46,-1.48 -56.61,-14.34 -76.43,-36.21 -19.82,-21.88 -29.94,-50.16 -28.46,-79.66l48.08,2.39c-0.82,16.64 4.88,32.6 16.07,44.95 11.17,12.35 26.52,19.6 43.13,20.42 16.57,0.75 32.6,-4.88 44.93,-16.06 12.35,-11.16 19.6,-26.49 20.43,-43.12 1.7,-34.34 -24.86,-63.67 -59.2,-65.37l2.4,-48.1c60.87,3.02 107.92,54.98 104.91,115.85 -1.47,29.48 -14.33,56.62 -36.2,76.44 -20.49,18.55 -46.6,28.59 -74.05,28.59h0Z" + android:fillColor="#1a5632" /> diff --git a/GenericApp/app/src/main/res/drawable/ic_logo_full_no_text_colour.xml b/GenericApp/app/src/main/res/drawable/ic_logo_full_no_text_colour.xml index 1ea4d8c..e0361b2 100644 --- a/GenericApp/app/src/main/res/drawable/ic_logo_full_no_text_colour.xml +++ b/GenericApp/app/src/main/res/drawable/ic_logo_full_no_text_colour.xml @@ -1,48 +1,42 @@ - + + android:pathData="M92.34,32.64c7.3,0 12.94,6.18 12.94,12.94s-5.55,12.94 -12.94,12.94 -12.85,-6.09 -12.85,-12.76c0,-7.8 6.36,-13.12 12.85,-13.12ZM92.39,55.68c4.78,0 9.74,-3.79 9.74,-10.23s-5.5,-9.96 -9.74,-9.96c-4.51,0 -9.74,3.7 -9.74,10.23 0,5.73 4.64,9.96 9.74,9.96Z" + android:fillColor="#006225" /> + android:pathData="M164.22,32.75h3.16v4.19h0.09c0.95,-1.49 3.38,-3.8 7.57,-3.8 3.02,0 5.55,0.77 7.8,3.03 1.85,1.81 3.07,4.66 3.07,8.73v13.62h-3.16v-13.62c0,-3.21 -0.99,-5.47 -2.35,-6.79 -1.71,-1.67 -3.97,-2.13 -5.37,-2.13s-3.65,0.45 -5.37,2.13c-1.35,1.31 -2.34,3.57 -2.3,6.79v13.62h-3.16v-25.78Z" + android:fillColor="#006225" /> + android:pathData="M190.17,33.5h5.5v2.21h0.09c1.13,-1.62 2.25,-3.07 5.37,-3.07h0.5v5.82c-5.45,0.23 -5.45,4.51 -5.45,6v14.07h-6v-25.02Z" + android:fillColor="#49a942" /> + android:pathData="M270.2,45.45c0,-7.58 6.18,-13.39 13.35,-13.39s13.26,5.64 13.26,13.3c0,8.21 -6.31,13.39 -13.26,13.39 -7.44,0 -13.35,-6.13 -13.35,-13.3ZM290.8,45.49c0,-4.55 -3.52,-7.94 -7.26,-7.94 -3.11,0 -7.35,2.66 -7.35,7.89 0,4.73 3.56,7.8 7.35,7.8 4.37,0 7.26,-3.79 7.26,-7.75Z" + android:fillColor="#49a942" /> + + android:pathData="M256.86,32.81c-3.05,0 -5.81,1.33 -7.8,3.47 -1.99,-2.14 -4.75,-3.47 -7.8,-3.47 -6.05,0 -10.97,5.22 -10.97,11.64v14.33h6.29v-14.33c0,-2.95 2.1,-5.34 4.67,-5.34s4.48,2.19 4.65,4.95c0,0.13 -0.02,0.26 -0.02,0.39v14.33h6.3v-0.04h0.04v-14.29c0,-0.13 -0.01,-0.26 -0.02,-0.39 0.18,-2.76 2.19,-4.95 4.65,-4.95s4.67,2.4 4.67,5.34v14.29h6.29v-14.29c0,-6.42 -4.92,-11.64 -10.97,-11.64Z" + android:fillColor="#49a942" /> + + android:pathData="M120.79,32.64c-6.49,0 -12.85,5.3 -12.87,13.08h3.16c0,-6.54 5.24,-10.24 9.75,-10.24 4.25,0 9.75,3.47 9.75,9.96s-4.97,10.23 -9.75,10.23c-2.38,0 -4.65,-0.93 -6.4,-2.49v3.61c1.86,1.08 4.03,1.72 6.36,1.72 7.41,0 12.96,-6.22 12.96,-12.94s-5.65,-12.94 -12.96,-12.94Z" + android:fillColor="#006225" /> + android:pathData="M159.6,38.95c-1.76,-3.07 -5.59,-6.27 -11,-6.31 -7.17,0.13 -12.8,6 -12.8,12.98s5.95,12.89 12.85,12.89c5.68,0 10.41,-3.74 12.13,-8.66h-3.34c-1.13,3.07 -4.33,5.73 -8.84,5.82 -4.82,0 -9.33,-4.1 -9.56,-8.93h0c-0.14,-1.31 -0.19,-3.28 0.47,-4.77h0c1.55,-4.46 5.7,-6.5 9.12,-6.5 3.92,0 8.66,2.62 9.65,8.43h-14.15v2.84h17.35c0.14,-2.39 -0.23,-5.14 -1.89,-7.8Z" + android:fillColor="#006225" /> + android:pathData="M227.79,45.08c0,-6.31 -5,-13.16 -12.94,-13.16s-13.16,6.49 -13.16,13.57 5.77,13.26 13.21,13.26c5.86,0 10.64,-4.19 12.08,-8.7h-6.31c-1.13,2.07 -3.11,3.2 -5.77,3.2 -4.15,0 -6.47,-3.29 -6.86,-4.97 -0.36,-1.11 -0.72,-2.93 -0.21,-4.99h-0.01c0.03,-0.12 0.07,-0.23 0.1,-0.35 0.01,-0.04 0.02,-0.07 0.03,-0.11 1.26,-4.26 4.81,-5.41 6.95,-5.41 2.97,0 6.09,1.85 6.76,5.86h-10.53v4.82h16.3c0.18,-0.81 0.36,-1.85 0.36,-3.02Z" + android:fillColor="#49a942" /> + android:pathData="M335.97,45.08c0,-6.31 -5,-13.16 -12.94,-13.16s-13.17,6.49 -13.17,13.57 5.77,13.26 13.21,13.26c5.86,0 10.64,-4.19 12.08,-8.7h-6.31c-1.13,2.07 -3.11,3.2 -5.77,3.2 -4.14,0 -6.47,-3.29 -6.86,-4.97 -0.36,-1.11 -0.72,-2.93 -0.21,-4.99h-0.01c0.03,-0.12 0.07,-0.23 0.1,-0.35 0.01,-0.04 0.02,-0.07 0.03,-0.11 1.26,-4.26 4.81,-5.41 6.95,-5.41 2.98,0 6.09,1.85 6.76,5.86h-10.91v4.82h16.68c0.18,-0.81 0.36,-1.85 0.36,-3.02Z" + android:fillColor="#49a942" /> + android:pathData="M38.8,73.19c-17.32,0 -31.42,-14.09 -31.42,-31.42h6.58c0,13.7 11.14,24.84 24.84,24.84s24.84,-11.14 24.84,-24.84 -11.14,-24.84 -24.84,-24.84v-6.58c17.32,0 31.42,14.09 31.42,31.41s-14.09,31.42 -31.42,31.42h0Z" + android:fillColor="#c1d82f" /> + android:pathData="M34.04,64.57c-6.09,-1.27 -11.32,-4.83 -14.72,-10.04 -3.41,-5.2 -4.58,-11.42 -3.31,-17.51 1.27,-6.09 4.84,-11.32 10.04,-14.72 5.2,-3.41 11.42,-4.58 17.51,-3.31 12.57,2.62 20.66,14.98 18.04,27.54l-6.44,-1.34c1.88,-9.02 -3.93,-17.88 -12.94,-19.77 -4.37,-0.91 -8.83,-0.07 -12.56,2.38 -3.73,2.44 -6.29,6.19 -7.2,10.56 -0.91,4.37 -0.07,8.83 2.38,12.56 2.44,3.73 6.19,6.29 10.56,7.2l-1.34,6.44h0Z" + android:fillColor="#49a942" /> - - + android:pathData="M38.82,56.87c-0.25,0 -0.51,0 -0.77,-0.02 -4.02,-0.2 -7.73,-1.96 -10.44,-4.95 -2.71,-2.99 -4.09,-6.85 -3.89,-10.88l6.57,0.33c-0.11,2.27 0.67,4.45 2.2,6.14 1.53,1.69 3.62,2.68 5.89,2.79 2.26,0.1 4.45,-0.67 6.14,-2.19 1.69,-1.52 2.68,-3.62 2.79,-5.89 0.23,-4.69 -3.4,-8.7 -8.09,-8.93l0.33,-6.57c8.32,0.41 14.74,7.51 14.33,15.82 -0.2,4.03 -1.96,7.73 -4.95,10.44 -2.8,2.53 -6.37,3.91 -10.11,3.91h0Z" + android:fillColor="#006225" /> diff --git a/GenericApp/app/src/main/res/drawable/ic_menu.xml b/GenericApp/app/src/main/res/drawable/ic_menu.xml index e1c8eb1..87c589d 100644 --- a/GenericApp/app/src/main/res/drawable/ic_menu.xml +++ b/GenericApp/app/src/main/res/drawable/ic_menu.xml @@ -1,9 +1,4 @@ - - - \ No newline at end of file + + + diff --git a/GenericApp/app/src/main/res/drawable/ic_plus_green.xml b/GenericApp/app/src/main/res/drawable/ic_plus_green.xml index a286070..1ececcd 100644 --- a/GenericApp/app/src/main/res/drawable/ic_plus_green.xml +++ b/GenericApp/app/src/main/res/drawable/ic_plus_green.xml @@ -1,10 +1,5 @@ - - + + diff --git a/GenericApp/app/src/main/res/drawable/notification_icon.xml b/GenericApp/app/src/main/res/drawable/notification_icon.xml index 37d07f4..97e7cbf 100644 --- a/GenericApp/app/src/main/res/drawable/notification_icon.xml +++ b/GenericApp/app/src/main/res/drawable/notification_icon.xml @@ -1,4 +1,3 @@ - - \ No newline at end of file + + diff --git a/GenericApp/app/src/main/res/drawable/rounded_corner.xml b/GenericApp/app/src/main/res/drawable/rounded_corner.xml index ba949aa..d363de4 100644 --- a/GenericApp/app/src/main/res/drawable/rounded_corner.xml +++ b/GenericApp/app/src/main/res/drawable/rounded_corner.xml @@ -1,5 +1,5 @@ - - + + diff --git a/GenericApp/app/src/main/res/layout/activity_main.xml b/GenericApp/app/src/main/res/layout/activity_main.xml index 1548859..332a705 100644 --- a/GenericApp/app/src/main/res/layout/activity_main.xml +++ b/GenericApp/app/src/main/res/layout/activity_main.xml @@ -1,32 +1,16 @@ + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_web" android:layout_width="match_parent" + android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" android:padding="0dp" + tools:context="io.openremote.orlib.ui.MainActivity"> - - + + - + diff --git a/GenericApp/app/src/main/res/layout/activity_privacy_policy.xml b/GenericApp/app/src/main/res/layout/activity_privacy_policy.xml index 7378056..38b3dcb 100644 --- a/GenericApp/app/src/main/res/layout/activity_privacy_policy.xml +++ b/GenericApp/app/src/main/res/layout/activity_privacy_policy.xml @@ -1,56 +1,30 @@ - + - + - + - + -