Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement suggestion area in app settings #3640

Merged
merged 1 commit into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.homeassistant.companion.android.settings

import android.annotation.SuppressLint
import android.app.UiModeManager
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
Expand Down Expand Up @@ -107,6 +108,29 @@ class SettingsFragment(
}
}

lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
presenter.getSuggestionFlow().collect { suggestion ->
findPreference<SettingsSuggestionPreference>("settings_suggestion")?.let {
if (suggestion != null) {
it.setTitle(suggestion.title)
it.setSummary(suggestion.summary)
it.setIcon(suggestion.icon)
it.setOnPreferenceClickListener {
when (suggestion.id) {
SettingsPresenter.SUGGESTION_ASSISTANT_APP -> updateAssistantApp()
SettingsPresenter.SUGGESTION_NOTIFICATION_PERMISSION -> openNotificationSettings()
}
return@setOnPreferenceClickListener true
}
it.setOnPreferenceCancelListener { presenter.cancelSuggestion(requireContext(), suggestion.id) }
}
it.isVisible = suggestion != null
}
}
}
}

findPreference<Preference>("server_add")?.let {
it.setOnPreferenceClickListener {
requestOnboardingResult.launch(
Expand Down Expand Up @@ -320,6 +344,14 @@ class SettingsFragment(
}
}

private fun updateAssistantApp() {
// On Android Q+, this is a workaround as Android doesn't allow requesting the assistant role
val openIntent = Intent(Intent.ACTION_MAIN)
openIntent.component = ComponentName("com.android.settings", "com.android.settings.Settings\$ManageAssistActivity")
openIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(openIntent)
}

private fun updateBackgroundAccessPref() {
findPreference<Preference>("background")?.let {
if (isIgnoringBatteryOptimizations()) {
Expand Down Expand Up @@ -482,6 +514,7 @@ class SettingsFragment(
override fun onResume() {
super.onResume()
activity?.title = getString(commonR.string.companion_app)
context?.let { presenter.updateSuggestions(it) }
}

override fun onDestroy() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.homeassistant.companion.android.settings

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes

data class SettingsHomeSuggestion(
val id: String,
@StringRes val title: Int,
@StringRes val summary: Int,
@DrawableRes val icon: Int
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@ import io.homeassistant.companion.android.onboarding.OnboardApp
import kotlinx.coroutines.flow.StateFlow

interface SettingsPresenter {
companion object {
const val SUGGESTION_ASSISTANT_APP = "assistant_app"
const val SUGGESTION_NOTIFICATION_PERMISSION = "notification_permission"
}

fun init(view: SettingsView)
fun getPreferenceDataStore(): PreferenceDataStore
fun onFinish()
fun updateSuggestions(context: Context)
fun cancelSuggestion(context: Context, id: String)
suspend fun addServer(result: OnboardApp.Output?)
fun getSuggestionFlow(): StateFlow<SettingsHomeSuggestion?>
fun getServersFlow(): StateFlow<List<Server>>
fun getServerCount(): Int
suspend fun getNotificationRateLimits(): RateLimitResponse?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package io.homeassistant.companion.android.settings

import android.app.role.RoleManager
import android.content.Context
import android.os.Build
import android.provider.Settings
import android.util.Log
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.preference.PreferenceDataStore
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.integration.DeviceRegistration
import io.homeassistant.companion.android.common.data.integration.impl.entities.RateLimitResponse
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
Expand All @@ -29,11 +35,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import javax.inject.Inject
import io.homeassistant.companion.android.common.R as commonR

class SettingsPresenterImpl @Inject constructor(
private val serverManager: ServerManager,
Expand All @@ -53,6 +61,8 @@ class SettingsPresenterImpl @Inject constructor(

private lateinit var view: SettingsView

private var suggestionFlow = MutableStateFlow<SettingsHomeSuggestion?>(null)

override fun getBoolean(key: String, defValue: Boolean): Boolean = runBlocking {
return@runBlocking when (key) {
"fullscreen" -> prefsRepository.isFullScreenEnabled()
Expand Down Expand Up @@ -111,6 +121,8 @@ class SettingsPresenterImpl @Inject constructor(
mainScope.cancel()
}

override fun getSuggestionFlow(): StateFlow<SettingsHomeSuggestion?> = suggestionFlow

override fun getServersFlow(): StateFlow<List<Server>> = serverManager.defaultServersFlow

override fun getServerCount(): Int = serverManager.defaultServers.size
Expand Down Expand Up @@ -206,4 +218,58 @@ class SettingsPresenterImpl @Inject constructor(
)
}
}

override fun updateSuggestions(context: Context) {
mainScope.launch { getSuggestions(context, false) }
}

override fun cancelSuggestion(context: Context, id: String) {
mainScope.launch {
val ignored = prefsRepository.getIgnoredSuggestions()
if (!ignored.contains(id)) {
prefsRepository.setIgnoredSuggestions(ignored + id)
}
getSuggestions(context, true)
}
}

private suspend fun getSuggestions(context: Context, overwrite: Boolean) {
val suggestions = mutableListOf<SettingsHomeSuggestion>()

// Assist
var assistantSuggestion = serverManager.defaultServers.any { it.version?.isAtLeast(2023, 5) == true }
if (assistantSuggestion && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val roleManager = context.getSystemService<RoleManager>()
assistantSuggestion = roleManager?.isRoleAvailable(RoleManager.ROLE_ASSISTANT) == true && !roleManager.isRoleHeld(RoleManager.ROLE_ASSISTANT)
} else if (assistantSuggestion && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val defaultApp: String? = Settings.Secure.getString(context.contentResolver, "assistant")
assistantSuggestion = defaultApp?.contains(BuildConfig.APPLICATION_ID) == false
}
if (assistantSuggestion) {
suggestions += SettingsHomeSuggestion(
SettingsPresenter.SUGGESTION_ASSISTANT_APP,
commonR.string.suggestion_assist_title,
commonR.string.suggestion_assist_summary,
R.drawable.ic_comment_processing_outline
)
}

// Notifications
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !NotificationManagerCompat.from(context).areNotificationsEnabled()) {
suggestions += SettingsHomeSuggestion(
SettingsPresenter.SUGGESTION_NOTIFICATION_PERMISSION,
commonR.string.suggestion_notifications_title,
commonR.string.suggestion_notifications_summary,
commonR.drawable.ic_notifications
)
}

val ignored = prefsRepository.getIgnoredSuggestions()
val filteredSuggestions = suggestions.filter { !ignored.contains(it.id) }
if (overwrite || suggestionFlow.value == null) {
suggestionFlow.emit(filteredSuggestions.randomOrNull())
} else if (filteredSuggestions.none { it.id == suggestionFlow.value?.id }) {
suggestionFlow.emit(null)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.homeassistant.companion.android.settings

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.ImageButton
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import io.homeassistant.companion.android.R

class SettingsSuggestionPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet,
defStyleAttr: Int = 0
) : Preference(context, attrs, defStyleAttr) {

private var onCancelClickListener: View.OnClickListener? = null

init {
layoutResource = R.layout.preference_suggestion
}

override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
holder.itemView.findViewById<ImageButton>(R.id.cancel)?.setOnClickListener(onCancelClickListener)
}

fun setOnPreferenceCancelListener(listener: View.OnClickListener?) {
onCancelClickListener = listener
}
}
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_comment_processing_outline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/colorAccent"
android:pathData="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M17,11H15V9H17V11M13,11H11V9H13V11M9,11H7V9H9V11Z" />
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorPreferenceSuggestion"/>
<corners android:radius="@dimen/bottom_sheet_corner_radius"/>
</shape>
65 changes: 65 additions & 0 deletions app/src/main/res/layout/preference_suggestion.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="?android:attr/selectableItemBackground"
android:animateLayoutChanges="true"
android:padding="16dp">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="@drawable/preference_suggestion_background">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@android:id/icon"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginBottom="4dp"
android:contentDescription="@null"
tools:src="@drawable/ic_comment_processing_outline"/>

<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />

<ImageButton
android:id="@+id/cancel"
android:layout_width="24dp"
android:layout_height="24dp"
android:padding="4dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_clear_black"
android:scaleType="fitCenter"
android:contentDescription="@string/cancel"
app:tint="?attr/colorControlNormal" />

</LinearLayout>

<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
tools:text="@string/suggestion_assist_title"/>

<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
tools:text="@string/suggestion_assist_summary"/>

</LinearLayout>

</FrameLayout>
3 changes: 3 additions & 0 deletions app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<io.homeassistant.companion.android.settings.SettingsSuggestionPreference
android:key="settings_suggestion"
app:isPreferenceVisible="false" />
<PreferenceCategory
android:key="servers_devices_category"
android:title="@string/servers_devices_category">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ interface PrefsRepository {
suspend fun saveKeyAlias(alias: String)

suspend fun getKeyAlias(): String?

suspend fun getIgnoredSuggestions(): List<String>

suspend fun setIgnoredSuggestions(ignored: List<String>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class PrefsRepositoryImpl @Inject constructor(
private const val PREF_WEBVIEW_DEBUG_ENABLED = "webview_debug_enabled"
private const val PREF_KEY_ALIAS = "key-alias"
private const val PREF_CRASH_REPORTING_DISABLED = "crash_reporting"
private const val PREF_IGNORED_SUGGESTIONS = "ignored_suggestions"
}

init {
Expand Down Expand Up @@ -188,4 +189,12 @@ class PrefsRepositoryImpl @Inject constructor(
override suspend fun getKeyAlias(): String? {
return localStorage.getString(PREF_KEY_ALIAS)
}

override suspend fun getIgnoredSuggestions(): List<String> {
return localStorage.getStringSet(PREF_IGNORED_SUGGESTIONS)?.toList() ?: emptyList()
}

override suspend fun setIgnoredSuggestions(ignored: List<String>) {
localStorage.putStringSet(PREF_IGNORED_SUGGESTIONS, ignored.toSet())
}
}
1 change: 1 addition & 0 deletions common/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<color name="colorDeviceControlsThermostatHeat">#FF8B66</color>
<color name="colorDeviceControlsCamera">#F1F3F4</color>
<color name="colorSpeechText">#B3E5FC</color>
<color name="colorPreferenceSuggestion">#1F03A9F4</color> <!-- colorAccent 12% opacity -->
<color name="colorOnSurfaceVariant">#49454E</color> <!-- M3 On Surface Variant -->
<color name="colorBottomSheetHandle">#6649454E</color> <!-- M3 On Surface Variant 40% opacity -->
</resources>
4 changes: 4 additions & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,10 @@
<string name="store_request_successful">Request to install app on wear device sent successfully</string>
<string name="store_request_unsuccessful">Play Store Request Failed. Wear device(s) may not support Play Store, that is, the Wear device may be version 1.0.</string>
<string name="successful">Successful</string>
<string name="suggestion_assist_title">Use Assist from anywhere</string>
<string name="suggestion_assist_summary">Set Home Assistant as your assistant app</string>
<string name="suggestion_notifications_title">Enable notifications</string>
<string name="suggestion_notifications_summary">Allow Home Assistant to send notifications</string>
<string name="sun">Sun</string>
<string name="switches">Switches</string>
<string name="tag_reader_title">Processing Tag</string>
Expand Down