diff --git a/mobile/src/beta/res/xml/day_dream.xml b/mobile/src/beta/res/xml/day_dream.xml new file mode 100644 index 0000000000..a116f55d37 --- /dev/null +++ b/mobile/src/beta/res/xml/day_dream.xml @@ -0,0 +1,3 @@ + + diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 7582d6e690..f0d30ebe3c 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -91,6 +91,10 @@ + + + + + + + + + + diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt b/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt new file mode 100644 index 0000000000..5d297d7522 --- /dev/null +++ b/mobile/src/main/java/org/openhab/habdroid/ui/DayDream.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.habdroid.ui + +import android.animation.ObjectAnimator +import android.service.dreams.DreamService +import android.util.Log +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import kotlin.coroutines.CoroutineContext +import kotlin.random.Random +import kotlin.time.Duration.Companion.minutes +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import org.json.JSONException +import org.json.JSONObject +import org.openhab.habdroid.R +import org.openhab.habdroid.core.connection.ConnectionFactory +import org.openhab.habdroid.util.HttpClient +import org.openhab.habdroid.util.ItemClient +import org.openhab.habdroid.util.PrefKeys +import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getStringOrNull + +class DayDream : DreamService(), CoroutineScope { + private val job = Job() + override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job + private lateinit var textView: TextView + private lateinit var wrapper: LinearLayout + private lateinit var container: FrameLayout + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + isInteractive = false + isFullscreen = true + isScreenBright = applicationContext.getPrefs().getBoolean(PrefKeys.DAY_DREAM_BRIGHT_SCREEN, true) + setContentView(R.layout.daydream) + } + + override fun onDreamingStarted() { + super.onDreamingStarted() + val item = applicationContext.getPrefs().getStringOrNull(PrefKeys.DAY_DREAM_ITEM) + + textView = findViewById(R.id.text) + wrapper = findViewById(R.id.wrapper) + container = findViewById(R.id.container) + + launch { + item?.let { listenForTextItem(it) } + } + launch { + moveText() + } + } + + private suspend fun listenForTextItem(item: String) { + ConnectionFactory.waitForInitialization() + val connection = ConnectionFactory.primaryUsableConnection?.connection ?: return + + val eventSubscription = connection.httpClient.makeSse( + // Support for both the "openhab" and the older "smarthome" root topic by using a wildcard + connection.httpClient.buildUrl("rest/events?topics=*/items/$item/command") + ) + + textView.text = try { + ItemClient.loadItem(connection, item)?.state?.asString.orEmpty() + } catch (e: HttpClient.HttpException) { + getString(R.string.screensaver_error_loading_item, item) + } + + try { + while (isActive) { + try { + val event = JSONObject(eventSubscription.getNextEvent()) + if (event.optString("type") == "ALIVE") { + Log.d(TAG, "Got ALIVE event") + continue + } + val topic = event.getString("topic") + val topicPath = topic.split('/') + // Possible formats: + // - openhab/items//statechanged + // - openhab/items///statechanged + // When an update for a group is sent, there's also one for the individual item. + // Therefore always take the element on index two. + if (topicPath.size !in 4..5) { + throw JSONException("Unexpected topic path $topic") + } + val state = JSONObject(event.getString("payload")).getString("value") + Log.d(TAG, "Got state by event: $state") + textView.text = state + } catch (e: JSONException) { + Log.e(TAG, "Failed parsing JSON of state change event", e) + } + } + } finally { + eventSubscription.cancel() + } + } + + private suspend fun moveText() { + wrapper.fadeOut() + wrapper.moveViewToRandomPosition(container) + wrapper.fadeIn() + delay(1.minutes) + moveText() + } + + private fun View.moveViewToRandomPosition(container: FrameLayout) { + val randomX = Random.nextInt(0, container.width - width) + val randomY = Random.nextInt(0, container.height - height) + + x = randomX.toFloat() + y = randomY.toFloat() + } + + private fun View.fadeOut() { + val animator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f) + animator.duration = 2000 + animator.start() + } + + private fun View.fadeIn() { + val animator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f) + animator.duration = 2000 + animator.start() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + job.cancel() + } + + companion object { + private val TAG = DayDream::class.java.simpleName + } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/DayDreamPreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/DayDreamPreferencesActivity.kt new file mode 100644 index 0000000000..1fa1b03bc6 --- /dev/null +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/DayDreamPreferencesActivity.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.habdroid.ui.preference + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +class DayDreamPreferencesActivity : AppCompatActivity() { + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val intent = Intent(this, PreferencesActivity::class.java) + intent.action = PreferencesActivity.ACTION_DAY_DREAM + startActivity(intent) + } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt index b1b5aff9c6..aa8ca17bc3 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt @@ -31,6 +31,7 @@ import org.openhab.habdroid.R import org.openhab.habdroid.background.tiles.AbstractTileService import org.openhab.habdroid.ui.AbstractBaseActivity import org.openhab.habdroid.ui.preference.fragments.AbstractSettingsFragment +import org.openhab.habdroid.ui.preference.fragments.DayDreamFragment import org.openhab.habdroid.ui.preference.fragments.MainSettingsFragment import org.openhab.habdroid.ui.preference.fragments.TileOverviewFragment import org.openhab.habdroid.ui.preference.fragments.TileSettingsFragment @@ -70,6 +71,9 @@ class PreferencesActivity : AbstractBaseActivity() { ) ?: AppWidgetManager.INVALID_APPWIDGET_ID WidgetSettingsFragment.newInstance(id) } + intent.action == ACTION_DAY_DREAM -> { + DayDreamFragment() + } else -> { MainSettingsFragment() } @@ -175,6 +179,7 @@ class PreferencesActivity : AbstractBaseActivity() { } companion object { + const val ACTION_DAY_DREAM = "day_dream" const val RESULT_EXTRA_THEME_CHANGED = "theme_changed" const val RESULT_EXTRA_SITEMAP_CLEARED = "sitemap_cleared" const val RESULT_EXTRA_SITEMAP_DRAWER_CHANGED = "sitemap_drawer_changed" diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DayDreamFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DayDreamFragment.kt new file mode 100644 index 0000000000..a0278304ce --- /dev/null +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DayDreamFragment.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.habdroid.ui.preference.fragments + +import android.os.Bundle +import androidx.annotation.StringRes +import org.openhab.habdroid.R + +class DayDreamFragment : AbstractSettingsFragment() { + override val titleResId: Int @StringRes get() = R.string.screensaver + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_day_dream) + } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt index 6a4aa08d29..168a319030 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt @@ -90,6 +90,9 @@ object PrefKeys { const val DRAWER_ENTRY_NFC = "show_nfc" const val DRAWER_ENTRY_FRONTAIL = "show_frontail" + const val DAY_DREAM_ITEM = "daydream_item" + const val DAY_DREAM_BRIGHT_SCREEN = "daydream_bright_screen" + /** * Application state flags */ diff --git a/mobile/src/main/res/layout/daydream.xml b/mobile/src/main/res/layout/daydream.xml new file mode 100644 index 0000000000..fb1df6886b --- /dev/null +++ b/mobile/src/main/res/layout/daydream.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 4ca9b7b4bb..ed37634802 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -157,6 +157,10 @@ Android device control Description in tiles Depending on your Android version these tiles are located at long-press menu of the power button or next to the quick tiles + Screensaver + Displayed Item + Bright screen + Error loading Item \"%s\" Voice commands diff --git a/mobile/src/main/res/xml/preferences_day_dream.xml b/mobile/src/main/res/xml/preferences_day_dream.xml new file mode 100644 index 0000000000..6bbd9bd6aa --- /dev/null +++ b/mobile/src/main/res/xml/preferences_day_dream.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/mobile/src/stable/res/xml/day_dream.xml b/mobile/src/stable/res/xml/day_dream.xml new file mode 100644 index 0000000000..ba37f5eb69 --- /dev/null +++ b/mobile/src/stable/res/xml/day_dream.xml @@ -0,0 +1,3 @@ + +