mirror of
https://github.com/home-assistant/android
synced 2024-07-22 10:54:12 +00:00
Server gestures and chooser (#3346)
* Server gestures and chooser - Add gestures to quickly switch between multiple servers - Add a chooser to select a server, and add a gesture for it * Fix history (new page is added _after_ loading finishes)
This commit is contained in:
parent
7412dd22ab
commit
4bda27387c
|
@ -0,0 +1,62 @@
|
|||
package io.homeassistant.companion.android.settings.server
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||
import com.google.android.material.R
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ServerChooserFragment : BottomSheetDialogFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var serverManager: ServerManager
|
||||
|
||||
companion object {
|
||||
const val TAG = "ServerChooser"
|
||||
|
||||
const val RESULT_KEY = "ServerChooserResult"
|
||||
const val RESULT_SERVER = "server"
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
MdcTheme {
|
||||
ServerChooserView(
|
||||
servers = serverManager.defaultServers,
|
||||
onServerSelected = { serverId ->
|
||||
setFragmentResult(RESULT_KEY, bundleOf(RESULT_SERVER to serverId))
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
dialog?.setOnShowListener {
|
||||
val dialog = it as BottomSheetDialog
|
||||
val bottomSheet = dialog.findViewById<View>(R.id.design_bottom_sheet) as FrameLayout
|
||||
val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package io.homeassistant.companion.android.settings.server
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.homeassistant.companion.android.database.server.Server
|
||||
import io.homeassistant.companion.android.util.compose.ModalBottomSheet
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@Composable
|
||||
fun ServerChooserView(
|
||||
servers: List<Server>,
|
||||
onServerSelected: (Int) -> Unit
|
||||
) {
|
||||
ModalBottomSheet(title = stringResource(commonR.string.server_select)) {
|
||||
servers.forEach {
|
||||
ServerChooserRow(server = it, onServerSelected = onServerSelected)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ServerChooserRow(
|
||||
server: Server,
|
||||
onServerSelected: (Int) -> Unit
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 56.dp)
|
||||
.clickable { onServerSelected(server.id) }
|
||||
) {
|
||||
Text(
|
||||
text = server.friendlyName,
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
Divider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
}
|
|
@ -18,6 +18,7 @@ import androidx.fragment.app.commit
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.Preference.OnPreferenceClickListener
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
|
@ -81,15 +82,20 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
if (presenter.hasMultipleServers()) {
|
||||
val activateClickListener = OnPreferenceClickListener {
|
||||
val intent = WebViewActivity.newInstance(requireContext(), null, serverId).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
requireContext().startActivity(intent)
|
||||
return@OnPreferenceClickListener true
|
||||
}
|
||||
findPreference<Preference>("activate_server")?.let {
|
||||
it.isVisible = true
|
||||
it.setOnPreferenceClickListener {
|
||||
val intent = WebViewActivity.newInstance(requireContext(), null, serverId).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
requireContext().startActivity(intent)
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
it.onPreferenceClickListener = activateClickListener
|
||||
}
|
||||
findPreference<Preference>("activate_server_hint")?.let {
|
||||
it.isVisible = true
|
||||
it.onPreferenceClickListener = activateClickListener
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package io.homeassistant.companion.android.util.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
/**
|
||||
* A Material 3-style modal bottom sheet with an optional handle, for use with a
|
||||
* [com.google.android.material.bottomsheet.BottomSheetDialogFragment].
|
||||
*/
|
||||
@Composable
|
||||
fun ModalBottomSheet(
|
||||
title: String,
|
||||
showHandle: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val sheetCornerRadius = dimensionResource(R.dimen.bottom_sheet_corner_radius)
|
||||
Surface(
|
||||
shape = RoundedCornerShape(topStart = sheetCornerRadius, topEnd = sheetCornerRadius)
|
||||
) {
|
||||
Column {
|
||||
if (showHandle) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 22.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 24.dp, height = 4.dp)
|
||||
.clip(RoundedCornerShape(2.dp))
|
||||
.background(colorResource(commonR.color.colorBottomSheetHandle))
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 22.dp),
|
||||
style = MaterialTheme.typography.h6,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -90,6 +90,7 @@ import io.homeassistant.companion.android.nfc.WriteNfcTag
|
|||
import io.homeassistant.companion.android.sensors.SensorReceiver
|
||||
import io.homeassistant.companion.android.sensors.SensorWorker
|
||||
import io.homeassistant.companion.android.settings.SettingsActivity
|
||||
import io.homeassistant.companion.android.settings.server.ServerChooserFragment
|
||||
import io.homeassistant.companion.android.themes.ThemesManager
|
||||
import io.homeassistant.companion.android.util.ChangeLog
|
||||
import io.homeassistant.companion.android.util.DataUriDownloadManager
|
||||
|
@ -211,6 +212,7 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
|
|||
private var exoBottom: Int = 0
|
||||
private var exoMute: Boolean = true
|
||||
private var failedConnection = "external"
|
||||
private var clearHistory = false
|
||||
private var moreInfoEntity = ""
|
||||
private val moreInfoMutex = Mutex()
|
||||
private var currentAutoplay: Boolean = false
|
||||
|
@ -278,11 +280,24 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
|
|||
direction: SwipeDirection,
|
||||
pointerCount: Int
|
||||
): Boolean {
|
||||
if (pointerCount == 3 &&
|
||||
direction == SwipeDirection.DOWN &&
|
||||
velocity >= 150
|
||||
) {
|
||||
dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_E))
|
||||
if (pointerCount == 3 && velocity >= 75) {
|
||||
when (direction) {
|
||||
SwipeDirection.LEFT -> presenter.nextServer()
|
||||
SwipeDirection.RIGHT -> presenter.previousServer()
|
||||
SwipeDirection.UP -> {
|
||||
val serverChooser = ServerChooserFragment()
|
||||
supportFragmentManager.setFragmentResultListener(ServerChooserFragment.RESULT_KEY, this@WebViewActivity) { _, bundle ->
|
||||
if (bundle.containsKey(ServerChooserFragment.RESULT_SERVER)) {
|
||||
presenter.switchActiveServer(bundle.getInt(ServerChooserFragment.RESULT_SERVER))
|
||||
}
|
||||
supportFragmentManager.clearFragmentResultListener(ServerChooserFragment.RESULT_KEY)
|
||||
}
|
||||
serverChooser.show(supportFragmentManager, ServerChooserFragment.TAG)
|
||||
}
|
||||
SwipeDirection.DOWN -> {
|
||||
dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_E))
|
||||
}
|
||||
}
|
||||
}
|
||||
return appLocked
|
||||
}
|
||||
|
@ -311,6 +326,10 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
|
|||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
if (clearHistory) {
|
||||
webView.clearHistory()
|
||||
clearHistory = false
|
||||
}
|
||||
enablePinchToZoom()
|
||||
if (moreInfoEntity != "" && view?.progress == 100 && isConnected) {
|
||||
ioScope.launch {
|
||||
|
@ -1072,9 +1091,9 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
|
|||
|
||||
override fun loadUrl(url: String, keepHistory: Boolean) {
|
||||
loadedUrl = url
|
||||
clearHistory = !keepHistory
|
||||
webView.loadUrl(url)
|
||||
waitForConnection()
|
||||
if (!keepHistory) webView.clearHistory()
|
||||
}
|
||||
|
||||
override fun setStatusBarAndNavigationBarColor(statusBarColor: Int, navigationBarColor: Int) {
|
||||
|
|
|
@ -13,6 +13,9 @@ interface WebViewPresenter {
|
|||
fun getActiveServer(): Int
|
||||
fun updateActiveServer()
|
||||
fun setActiveServer(id: Int)
|
||||
fun switchActiveServer(id: Int)
|
||||
fun nextServer()
|
||||
fun previousServer()
|
||||
|
||||
fun onGetExternalAuth(context: Context, callback: String, force: Boolean)
|
||||
|
||||
|
|
|
@ -130,6 +130,27 @@ class WebViewPresenterImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun switchActiveServer(id: Int) {
|
||||
setActiveServer(id)
|
||||
onViewReady(null)
|
||||
}
|
||||
|
||||
override fun nextServer() = moveToServer(next = true)
|
||||
|
||||
override fun previousServer() = moveToServer(next = false)
|
||||
|
||||
private fun moveToServer(next: Boolean) {
|
||||
val servers = serverManager.defaultServers
|
||||
if (servers.size < 2) return
|
||||
val currentServerIndex = servers.indexOfFirst { it.id == serverId }
|
||||
if (currentServerIndex > -1) {
|
||||
var newServerIndex = if (next) currentServerIndex + 1 else currentServerIndex - 1
|
||||
if (newServerIndex == servers.size) newServerIndex = 0
|
||||
if (newServerIndex < 0) newServerIndex = servers.size - 1
|
||||
servers.getOrNull(newServerIndex)?.let { switchActiveServer(it.id) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkSecurityVersion() {
|
||||
mainScope.launch {
|
||||
try {
|
||||
|
|
|
@ -14,4 +14,9 @@
|
|||
<item name="android:windowLightStatusBar">@bool/isLightMode</item>
|
||||
<item name="android:windowLightNavigationBar">@bool/isLightMode</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.HomeAssistant.BottomSheetDialog" parent="ThemeOverlay.HomeAssistant.BottomSheetDialog.Base">
|
||||
<item name="android:navigationBarColor">@color/colorSurface</item>
|
||||
<item name="android:windowLightNavigationBar">@bool/isLightMode</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -2,4 +2,5 @@
|
|||
<resources>
|
||||
<dimen name="activity_margin">16dp</dimen>
|
||||
<dimen name="widget_corner_radius">8dp</dimen>
|
||||
<dimen name="bottom_sheet_corner_radius">28dp</dimen>
|
||||
</resources>
|
|
@ -18,6 +18,7 @@
|
|||
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
|
||||
<item name="alertDialogTheme">@style/Theme.HomeAssistant.Dialog.Alert</item>
|
||||
<item name="actionBarPopupTheme">@style/ThemeOverlay.MaterialComponents.Dark</item>
|
||||
<item name="bottomSheetDialogTheme">@style/ThemeOverlay.HomeAssistant.BottomSheetDialog</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.HomeAssistant.Config" parent="Theme.HomeAssistant" />
|
||||
|
@ -55,6 +56,12 @@
|
|||
<item name="popupTheme">@style/Theme.HomeAssistant.PopupTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.HomeAssistant.BottomSheetDialog" parent="ThemeOverlay.HomeAssistant.BottomSheetDialog.Base"/>
|
||||
|
||||
<style name="ThemeOverlay.HomeAssistant.BottomSheetDialog.Base" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
|
||||
<item name="android:backgroundTint">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.HomeAssistant.ConfigActionBar" parent="ThemeOverlay.MaterialComponents.ActionBar">
|
||||
<item name="android:background">@color/colorBackground</item>
|
||||
<item name="android:textColorPrimary">@color/colorOnBackground</item>
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
android:icon="@drawable/ic_launch"
|
||||
android:title="@string/server_activate"
|
||||
app:isPreferenceVisible="false"/>
|
||||
<Preference
|
||||
android:key="activate_server_hint"
|
||||
android:summary="@string/server_activate_swipe_hint"
|
||||
app:isPreferenceVisible="false"/>
|
||||
<PreferenceCategory
|
||||
android:title="@string/device_registration">
|
||||
<EditTextPreference
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<color name="colorDialogTitle">@android:color/white</color>
|
||||
<color name="colorBackground">#1c1c1c</color>
|
||||
<color name="colorOnBackground">#e1e1e1</color>
|
||||
<color name="colorSurface">#121212</color>
|
||||
<color name="colorPrimary">#03A9F4</color>
|
||||
<color name="colorPrimaryDark">#111111</color>
|
||||
<color name="colorAccent">#03A9F4</color>
|
||||
|
@ -22,4 +23,5 @@
|
|||
<color name="colorActionBarPopupBackground">#2B2B2B</color>
|
||||
<color name="colorLaunchScreenBackground">#111111</color>
|
||||
<color name="colorCodeBackground">#282828</color>
|
||||
<color name="colorBottomSheetHandle">#66CAC4D0</color> <!-- M3 On Surface Variant 40% opacity -->
|
||||
</resources>
|
|
@ -5,6 +5,7 @@
|
|||
<color name="colorDialogTitle">@android:color/black</color>
|
||||
<color name="colorBackground">@android:color/white</color>
|
||||
<color name="colorOnBackground">#212121</color>
|
||||
<color name="colorSurface">@android:color/white</color>
|
||||
<color name="colorPrimary">#03A9F4</color>
|
||||
<color name="colorPrimaryDark">#0288D1</color>
|
||||
<color name="colorAccent">#03A9F4</color>
|
||||
|
@ -33,4 +34,5 @@
|
|||
<color name="colorDeviceControlsThermostatHeat">#FF8B66</color>
|
||||
<color name="colorDeviceControlsCamera">#F1F3F4</color>
|
||||
<color name="colorSpeechText">#B3E5FC</color>
|
||||
<color name="colorBottomSheetHandle">#6649454E</color> <!-- M3 On Surface Variant 40% opacity -->
|
||||
</resources>
|
||||
|
|
|
@ -651,7 +651,8 @@
|
|||
<string name="sensors">Sensors</string>
|
||||
<string name="server_settings">Server Settings</string>
|
||||
<string name="servers_devices_category">Servers & Devices</string>
|
||||
<string name="server_activate">Activate Server</string>
|
||||
<string name="server_activate">Activate</string>
|
||||
<string name="server_activate_swipe_hint">Tip: Quickly activate using a three-finger swipe left, right, or up when viewing a server.</string>
|
||||
<string name="server_add">Add Server</string>
|
||||
<string name="server_add_success">Server added</string>
|
||||
<string name="server_add_failed">Couldn\'t add server</string>
|
||||
|
|
Loading…
Reference in a new issue