Repository: openhab/openhab-android (language: kotlin)

Issue:
Split PreferencesActivity.kt in smaller files
The file has grown to big and should be split into multiple smaller files:
* One "main" file
* One file per fragment


Discussion hints:
nan

Pull request: Split PreferencesActivity.kt in smaller files
Fixes #3013

Signed-off-by: mueller-ma <mueller-ma@users.noreply.github.com>

Test command: ./gradlew :mobile:testFossBetaDebugUnitTest --tests "org.openhab.habdroid.util.PreferencesUtilTest"

Fix patch:
diff --git a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt
index 6308260317..440b4d1f0d 100644
--- a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt
+++ b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt
@@ -19,7 +19,7 @@ import org.openhab.habdroid.R
 import org.openhab.habdroid.core.connection.CloudConnection
 import org.openhab.habdroid.core.connection.ConnectionFactory
 import org.openhab.habdroid.core.connection.NotACloudServerException
-import org.openhab.habdroid.ui.PushNotificationStatus
+import org.openhab.habdroid.ui.preference.PushNotificationStatus
 import org.openhab.habdroid.util.HttpClient
 import org.openhab.habdroid.util.PrefKeys
 import org.openhab.habdroid.util.getHumanReadableErrorMessage
diff --git a/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt
index e6ff2adc80..55af4fa87c 100644
--- a/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt
+++ b/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt
@@ -21,7 +21,7 @@ import org.openhab.habdroid.R
 import org.openhab.habdroid.core.connection.CloudConnection
 import org.openhab.habdroid.core.connection.ConnectionFactory
 import org.openhab.habdroid.core.connection.NotACloudServerException
-import org.openhab.habdroid.ui.PushNotificationStatus
+import org.openhab.habdroid.ui.preference.PushNotificationStatus
 import org.openhab.habdroid.util.HttpClient
 import org.openhab.habdroid.util.getHumanReadableErrorMessage
 import org.openhab.habdroid.util.getPrefs
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 45c82f853e..390fc104a3 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -64,7 +64,7 @@
         <meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />
 
         <activity
-            android:name=".ui.PreferencesActivity"
+            android:name=".ui.preference.PreferencesActivity"
             android:label="@string/app_preferences_name"
             android:exported="true">
             <intent-filter>
diff --git a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt
index 44ed57cae7..22ce532377 100644
--- a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt
@@ -67,7 +67,7 @@ import org.openhab.habdroid.core.OpenHabApplication
 import org.openhab.habdroid.model.NfcTag
 import org.openhab.habdroid.ui.TaskerItemPickerActivity
 import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget
-import org.openhab.habdroid.ui.preference.toItemUpdatePrefValue
+import org.openhab.habdroid.ui.preference.widgets.toItemUpdatePrefValue
 import org.openhab.habdroid.util.PendingIntent_Immutable
 import org.openhab.habdroid.util.PendingIntent_Mutable
 import org.openhab.habdroid.util.PrefKeys
diff --git a/mobile/src/main/java/org/openhab/habdroid/background/tiles/AbstractTileService.kt b/mobile/src/main/java/org/openhab/habdroid/background/tiles/AbstractTileService.kt
index df1516d4ef..205f7ad182 100644
--- a/mobile/src/main/java/org/openhab/habdroid/background/tiles/AbstractTileService.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/background/tiles/AbstractTileService.kt
@@ -44,7 +44,7 @@ import org.openhab.habdroid.R
 import org.openhab.habdroid.background.BackgroundTasksManager
 import org.openhab.habdroid.background.ItemUpdateWorker
 import org.openhab.habdroid.background.tiles.AbstractTileService.Companion.getPrefKeyForId
-import org.openhab.habdroid.ui.PreferencesActivity
+import org.openhab.habdroid.ui.preference.PreferencesActivity
 import org.openhab.habdroid.util.getPrefs
 
 @RequiresApi(Build.VERSION_CODES.N)
diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt
index 5ff7b2825b..cbeb1735cb 100644
--- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt
@@ -33,7 +33,7 @@ import org.openhab.habdroid.model.ServerConfiguration
 import org.openhab.habdroid.model.ServerPath
 import org.openhab.habdroid.model.putIconResource
 import org.openhab.habdroid.model.toOH2IconResource
-import org.openhab.habdroid.ui.PreferencesActivity
+import org.openhab.habdroid.ui.preference.PreferencesActivity
 import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget
 import org.openhab.habdroid.util.PrefKeys
 import org.openhab.habdroid.util.getConfiguredServerIds
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.kt
index b9649d4169..8c9ce20d69 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractBaseActivity.kt
@@ -45,6 +45,7 @@ import kotlinx.coroutines.Job
 import kotlinx.coroutines.asExecutor
 import org.openhab.habdroid.BuildConfig
 import org.openhab.habdroid.R
+import org.openhab.habdroid.ui.preference.PreferencesActivity
 import org.openhab.habdroid.util.PrefKeys
 import org.openhab.habdroid.util.ScreenLockMode
 import org.openhab.habdroid.util.getActivityThemeId
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt
index 823931fdb5..90184f1898 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt
@@ -102,7 +102,8 @@ import org.openhab.habdroid.ui.activity.ContentController
 import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget
 import org.openhab.habdroid.ui.homescreenwidget.VoiceWidget
 import org.openhab.habdroid.ui.homescreenwidget.VoiceWidgetWithIcon
-import org.openhab.habdroid.ui.preference.toItemUpdatePrefValue
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+import org.openhab.habdroid.ui.preference.widgets.toItemUpdatePrefValue
 import org.openhab.habdroid.ui.widget.LockableDrawerLayout
 import org.openhab.habdroid.util.AsyncServiceResolver
 import org.openhab.habdroid.util.CrashReportingHelper
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt
deleted file mode 100644
index a3ae114066..0000000000
--- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt
+++ /dev/null
@@ -1,1743 +0,0 @@
-/*
- * Copyright (c) 2010-2022 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.app.Dialog
-import android.app.KeyguardManager
-import android.appwidget.AppWidgetManager
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.PackageManager
-import android.media.RingtoneManager
-import android.net.Uri
-import android.nfc.NfcAdapter
-import android.os.Build
-import android.os.Bundle
-import android.provider.Settings
-import android.service.quicksettings.TileService
-import android.util.Log
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.ColorInt
-import androidx.annotation.DrawableRes
-import androidx.annotation.RequiresApi
-import androidx.annotation.StringRes
-import androidx.annotation.VisibleForTesting
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatDelegate
-import androidx.core.content.ContextCompat
-import androidx.core.content.edit
-import androidx.core.graphics.drawable.DrawableCompat
-import androidx.core.os.bundleOf
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.commit
-import androidx.preference.EditTextPreference
-import androidx.preference.ListPreference
-import androidx.preference.Preference
-import androidx.preference.PreferenceCategory
-import androidx.preference.PreferenceFragmentCompat
-import androidx.preference.SwitchPreference
-import androidx.preference.SwitchPreferenceCompat
-import androidx.preference.forEachIndexed
-import androidx.work.WorkManager
-import com.google.android.material.snackbar.Snackbar
-import com.jaredrummler.android.colorpicker.ColorPreferenceCompat
-import java.util.BitSet
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import org.openhab.habdroid.R
-import org.openhab.habdroid.background.BackgroundTasksManager
-import org.openhab.habdroid.background.BackgroundTasksManager.Companion.buildWorkerTagForServer
-import org.openhab.habdroid.background.EventListenerService
-import org.openhab.habdroid.background.tiles.AbstractTileService
-import org.openhab.habdroid.background.tiles.TileData
-import org.openhab.habdroid.background.tiles.getTileData
-import org.openhab.habdroid.background.tiles.putTileData
-import org.openhab.habdroid.core.CloudMessagingHelper
-import org.openhab.habdroid.core.connection.CloudConnection
-import org.openhab.habdroid.core.connection.ConnectionFactory
-import org.openhab.habdroid.model.Item
-import org.openhab.habdroid.model.ServerConfiguration
-import org.openhab.habdroid.model.ServerPath
-import org.openhab.habdroid.model.ServerProperties
-import org.openhab.habdroid.model.toOH2IconResource
-import org.openhab.habdroid.model.toWifiSsids
-import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget
-import org.openhab.habdroid.ui.preference.CustomInputTypePreference
-import org.openhab.habdroid.ui.preference.ItemAndStatePreference
-import org.openhab.habdroid.ui.preference.ItemUpdatingPreference
-import org.openhab.habdroid.ui.preference.NotificationPollingPreference
-import org.openhab.habdroid.ui.preference.SslClientCertificatePreference
-import org.openhab.habdroid.ui.preference.WifiSsidInputPreference
-import org.openhab.habdroid.util.CacheManager
-import org.openhab.habdroid.util.CrashReportingHelper
-import org.openhab.habdroid.util.HttpClient
-import org.openhab.habdroid.util.PrefKeys
-import org.openhab.habdroid.util.Util
-import org.openhab.habdroid.util.getConfiguredServerIds
-import org.openhab.habdroid.util.getDayNightMode
-import org.openhab.habdroid.util.getNextAvailableServerId
-import org.openhab.habdroid.util.getNotificationTone
-import org.openhab.habdroid.util.getPreference
-import org.openhab.habdroid.util.getPrefixForBgTasks
-import org.openhab.habdroid.util.getPrefs
-import org.openhab.habdroid.util.getPrimaryServerId
-import org.openhab.habdroid.util.getSecretPrefs
-import org.openhab.habdroid.util.getStringOrFallbackIfEmpty
-import org.openhab.habdroid.util.getStringOrNull
-import org.openhab.habdroid.util.isInstalled
-import org.openhab.habdroid.util.isTaskerPluginEnabled
-import org.openhab.habdroid.util.putPrimaryServerId
-import org.openhab.habdroid.util.updateDefaultSitemap
-
-/**
- * This is a class to provide preferences activity for application.
- */
-class PreferencesActivity : AbstractBaseActivity() {
-    private lateinit var resultIntent: Intent
-
-    public override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        setContentView(R.layout.activity_prefs)
-
-        setSupportActionBar(findViewById(R.id.openhab_toolbar))
-        supportActionBar?.setDisplayHomeAsUpEnabled(true)
-
-        if (savedInstanceState == null) {
-            resultIntent = Intent()
-            val fragment = when {
-                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
-                    intent.action == TileService.ACTION_QS_TILE_PREFERENCES -> {
-                    val tile = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
-                    val tileId: Int = tile?.className?.let { AbstractTileService.getIdFromClassName(it) } ?: 0
-                    if (tileId > 0) {
-                        TileSettingsFragment.newInstance(tileId)
-                    } else {
-                        TileOverviewFragment()
-                    }
-                }
-                intent.action == AppWidgetManager.ACTION_APPWIDGET_CONFIGURE -> {
-                    val id = intent?.extras?.getInt(
-                        AppWidgetManager.EXTRA_APPWIDGET_ID,
-                        AppWidgetManager.INVALID_APPWIDGET_ID
-                    ) ?: AppWidgetManager.INVALID_APPWIDGET_ID
-                    WidgetSettingsFragment.newInstance(id)
-                }
-                else -> {
-                    MainSettingsFragment()
-                }
-            }
-            supportFragmentManager.commit {
-                add(R.id.activity_content, fragment)
-            }
-        } else {
-            resultIntent = savedInstanceState.getParcelable(STATE_KEY_RESULT, Intent::class.java) ?: Intent()
-        }
-        setResult(RESULT_OK, resultIntent)
-    }
-
-    override fun onSaveInstanceState(outState: Bundle) {
-        super.onSaveInstanceState(outState)
-        outState.putParcelable(STATE_KEY_RESULT, resultIntent)
-    }
-
-    override fun onOptionsItemSelected(item: MenuItem): Boolean {
-        if (isFinishing) {
-            return true
-        }
-        return when (item.itemId) {
-            android.R.id.home -> {
-                onBackPressedDispatcher.onBackPressed()
-                true
-            }
-            else -> super.onOptionsItemSelected(item)
-        }
-    }
-
-    fun handleThemeChange() {
-        resultIntent.putExtra(RESULT_EXTRA_THEME_CHANGED, true)
-        recreate()
-    }
-
-    fun openSubScreen(subScreenFragment: AbstractSettingsFragment) {
-        supportFragmentManager.commit {
-            replace(R.id.activity_content, subScreenFragment)
-            addToBackStack(null)
-        }
-    }
-
-    @VisibleForTesting
-    abstract class AbstractSettingsFragment : PreferenceFragmentCompat() {
-        @get:StringRes
-        protected abstract val titleResId: Int
-
-        protected val parentActivity get() = activity as PreferencesActivity
-        protected val prefs get() = preferenceScreen.sharedPreferences!!
-        protected val secretPrefs get() = requireContext().getSecretPrefs()
-
-        override fun onStart() {
-            super.onStart()
-            parentActivity.supportActionBar?.setTitle(titleResId)
-        }
-
-        override fun onDisplayPreferenceDialog(preference: Preference) {
-            val showDialog: (DialogFragment) -> Unit = { fragment ->
-                @Suppress("DEPRECATION") // TODO: Replace deprecated function
-                fragment.setTargetFragment(this, 0)
-                fragment.show(parentFragmentManager, "SettingsFragment.DIALOG:${preference.key}")
-            }
-            if (preference is CustomDialogPreference) {
-                showDialog(preference.createDialog())
-            } else {
-                super.onDisplayPreferenceDialog(preference)
-            }
-        }
-
-        companion object {
-            /**
-             * Password is considered strong when it is at least 8 chars long and contains 3 from those
-             * 4 categories:
-             * * lowercase
-             * * uppercase
-             * * numerics
-             * * other
-             * @param password
-             */
-            @VisibleForTesting
-            fun isWeakPassword(password: String?): Boolean {
-                if (password == null || password.length < 8) {
-                    return true
-                }
-                val groups = BitSet()
-                password.forEach { c -> groups.set(when {
-                    Character.isLetter(c) && Character.isLowerCase(c) -> 0
-                    Character.isLetter(c) && Character.isUpperCase(c) -> 1
-                    Character.isDigit(c) -> 2
-                    else -> 3
-                }) }
-                return groups.cardinality() < 3
-            }
-        }
-    }
-
-    class MainSettingsFragment : AbstractSettingsFragment(), ConnectionFactory.UpdateListener {
-        override val titleResId: Int @StringRes get() = R.string.action_settings
-        @ColorInt var previousColor: Int = 0
-
-        private var notificationPollingPref: NotificationPollingPreference? = null
-        private var notificationStatusHint: Preference? = null
-        private var selectRingToneCallback = registerForActivityResult(
-            ActivityResultContracts.StartActivityForResult()
-        ) { result ->
-            Log.d(TAG, "selectRingToneCallback: $result")
-            val data = result.data ?: return@registerForActivityResult
-            val ringtoneUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
-            val ringtonePref = getPreference(PrefKeys.NOTIFICATION_TONE)
-            updateRingtonePreferenceSummary(ringtonePref, ringtoneUri)
-            prefs.edit {
-                putString(PrefKeys.NOTIFICATION_TONE, ringtoneUri?.toString() ?: "")
-            }
-        }
-
-        override fun onStart() {
-            super.onStart()
-            updateScreenLockStateAndSummary(prefs.getStringOrFallbackIfEmpty(PrefKeys.SCREEN_LOCK,
-                getString(R.string.settings_screen_lock_off_value)))
-            populateServerPrefs()
-            ConnectionFactory.addListener(this)
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                updateTileSummary()
-            }
-        }
-
-        override fun onStop() {
-            super.onStop()
-            ConnectionFactory.removeListener(this)
-        }
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(R.xml.preferences)
-
-            val addServerPref = getPreference("add_server")
-            val sendDeviceInfoPref = getPreference(PrefKeys.SUBSCREEN_SEND_DEVICE_INFO)
-            notificationPollingPref =
-                getPreference(PrefKeys.FOSS_NOTIFICATIONS_ENABLED) as NotificationPollingPreference
-            notificationStatusHint = getPreference(PrefKeys.NOTIFICATION_STATUS_HINT)
-            val drawerEntriesPrefs = getPreference(PrefKeys.DRAWER_ENTRIES)
-            val themePref = getPreference(PrefKeys.THEME)
-            val accentColorPref = getPreference(PrefKeys.ACCENT_COLOR) as ColorPreferenceCompat
-            val clearCachePref = getPreference(PrefKeys.CLEAR_CACHE)
-            val fullscreenPref = getPreference(PrefKeys.FULLSCREEN)
-            val launcherPref = getPreference(PrefKeys.LAUNCHER)
-            val iconFormatPref = getPreference(PrefKeys.ICON_FORMAT)
-            val ringtonePref = getPreference(PrefKeys.NOTIFICATION_TONE)
-            val vibrationPref = getPreference(PrefKeys.NOTIFICATION_VIBRATION)
-            val ringtoneVibrationPref = getPreference(PrefKeys.NOTIFICATION_TONE_VIBRATION)
-            val viewLogPref = getPreference(PrefKeys.LOG)
-            val screenLockPref = getPreference(PrefKeys.SCREEN_LOCK)
-            val tilePref = getPreference(PrefKeys.SUBSCREEN_TILE)
-            val crashReporting = getPreference(PrefKeys.CRASH_REPORTING)
-
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-                val dataSaverPref = getPreference(PrefKeys.DATA_SAVER) as SwitchPreference
-                dataSaverPref.setSwitchTextOff(R.string.data_saver_off_pre_n)
-            }
-
-            updateRingtonePreferenceSummary(ringtonePref, prefs.getNotificationTone())
-            updateVibrationPreferenceIcon(vibrationPref,
-                prefs.getStringOrNull(PrefKeys.NOTIFICATION_VIBRATION))
-
-            addServerPref.setOnPreferenceClickListener {
-                val nextServerId = prefs.getNextAvailableServerId()
-                val nextName = if (prefs.getConfiguredServerIds().isEmpty()) {
-                    getString(R.string.openhab)
-                } else {
-                    getString(R.string.settings_server_default_name, nextServerId)
-                }
-                val f = ServerEditorFragment.newInstance(
-                    ServerConfiguration(nextServerId, nextName, null, null, null, null, null, false)
-                )
-                parentActivity.openSubScreen(f)
-                true
-            }
-
-            sendDeviceInfoPref.setOnPreferenceClickListener {
-                parentActivity.openSubScreen(SendDeviceInfoSettingsFragment())
-                false
-            }
-
-            if (CloudMessagingHelper.isPollingBuild()) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_STATUS_HINT)
-            } else {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.FOSS_NOTIFICATIONS_ENABLED)
-            }
-            updateNotificationStatusSummaries()
-            notificationPollingPref?.setOnPreferenceChangeListener { _, _ ->
-                parentActivity.launch(Dispatchers.Main) {
-                    updateNotificationStatusSummaries()
-                }
-                true
-            }
-
-            drawerEntriesPrefs.setOnPreferenceClickListener {
-                parentActivity.openSubScreen(DrawerEntriesMenuFragment())
-                false
-            }
-
-            themePref.setOnPreferenceChangeListener { _, _ ->
-                // getDayNightMode() needs the new preference value, so delay the call until
-                // after this listener has returned
-                parentActivity.launch(Dispatchers.Main) {
-                    val mode = parentActivity.getPrefs().getDayNightMode(parentActivity)
-                    AppCompatDelegate.setDefaultNightMode(mode)
-                    parentActivity.handleThemeChange()
-                }
-                true
-            }
-
-            previousColor = prefs.getInt(accentColorPref.key, 0)
-            accentColorPref.setOnPreferenceChangeListener { _, newValue ->
-                parentFragmentManager.findFragmentByTag(accentColorPref.fragmentTag)?.let { dialog ->
-                    parentFragmentManager.commit(allowStateLoss = true) {
-                        remove(dialog)
-                    }
-                }
-                if (previousColor != newValue) {
-                    parentActivity.handleThemeChange()
-                }
-                true
-            }
-
-            clearCachePref.setOnPreferenceClickListener { pref ->
-                clearImageCache(pref.context)
-                true
-            }
-
-            if (!prefs.isTaskerPluginEnabled() && !isAutomationAppInstalled()) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.TASKER_PLUGIN_ENABLED)
-            }
-
-            if (CrashReportingHelper.canBeDisabledByUser()) {
-                crashReporting.setOnPreferenceClickListener {
-                    CrashReportingHelper.initialize(requireActivity().application)
-                    true
-                }
-            } else {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.CRASH_REPORTING)
-            }
-
-            viewLogPref.setOnPreferenceClickListener { preference ->
-                val logIntent = Intent(preference.context, LogActivity::class.java)
-                startActivity(logIntent)
-                true
-            }
-
-            fullscreenPref.setOnPreferenceChangeListener { _, newValue ->
-                (activity as AbstractBaseActivity).setFullscreen(newValue as Boolean)
-                true
-            }
-
-            launcherPref.setOnPreferenceChangeListener { pref, newValue ->
-                val context = pref.context
-                val launcherAlias = ComponentName(context, "${context.packageName}.ui.LauncherActivityAlias")
-                val newState = if (newValue as Boolean)
-                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                else
-                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED
-                context.packageManager.setComponentEnabledSetting(launcherAlias, newState, PackageManager.DONT_KILL_APP)
-                true
-            }
-
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                Log.d(TAG, "Removing notification prefs for < 25")
-                preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_TONE)
-                preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_VIBRATION)
-
-                ringtoneVibrationPref.setOnPreferenceClickListener { pref ->
-                    val i = Intent(Settings.ACTION_SETTINGS).apply {
-                        action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
-                        putExtra(Settings.EXTRA_APP_PACKAGE, pref.context.packageName)
-                    }
-                    startActivity(i)
-                    true
-                }
-            } else {
-                Log.d(TAG, "Removing notification prefs for >= 25")
-                preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_TONE_VIBRATION)
-
-                ringtonePref.setOnPreferenceClickListener { pref ->
-                    val currentTone = prefs.getNotificationTone()
-                    val chooserIntent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
-                        putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
-                        putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI)
-                        putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, pref.title)
-                        putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
-                        putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
-                        putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentTone)
-                    }
-                    selectRingToneCallback.launch(chooserIntent)
-                    true
-                }
-
-                vibrationPref.setOnPreferenceChangeListener { pref, newValue ->
-                    updateVibrationPreferenceIcon(pref, newValue as String?)
-                    true
-                }
-            }
-
-            screenLockPref.setOnPreferenceChangeListener { _, newValue ->
-                updateScreenLockStateAndSummary(newValue as String)
-                true
-            }
-
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                tilePref.setOnPreferenceClickListener {
-                    parentActivity.openSubScreen(TileOverviewFragment())
-                    false
-                }
-                updateTileSummary()
-            } else {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.SUBSCREEN_TILE)
-            }
-
-            val flags = activity
-                ?.intent
-                ?.getParcelableExtra(START_EXTRA_SERVER_PROPERTIES, ServerProperties::class.java)
-                ?.flags
-                ?: prefs.getInt(PrefKeys.PREV_SERVER_FLAGS, 0)
-
-            if (flags and ServerProperties.SERVER_FLAG_ICON_FORMAT_SUPPORT == 0 ||
-                flags and ServerProperties.SERVER_FLAG_SUPPORTS_ANY_FORMAT_ICON != 0) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.ICON_FORMAT)
-            } else {
-                iconFormatPref.setOnPreferenceChangeListener { pref, _ ->
-                    val context = pref.context
-                    clearImageCache(context)
-                    ItemUpdateWidget.updateAllWidgets(context)
-                    true
-                }
-            }
-            if (flags and ServerProperties.SERVER_FLAG_CHART_SCALING_SUPPORT == 0) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.CHART_SCALING)
-            }
-        }
-
-        private fun populateServerPrefs() {
-            val context = preferenceManager.context
-            val connCategory = getPreference("connection") as PreferenceCategory
-            (0 until connCategory.preferenceCount)
-                .map { index -> connCategory.getPreference(index) }
-                .filter { pref -> pref.key?.startsWith("server_") == true }
-                .forEach { pref -> connCategory.removePreference(pref) }
-
-            prefs.getConfiguredServerIds().forEach { serverId ->
-                val config = ServerConfiguration.load(prefs, secretPrefs, serverId)
-                if (config != null) {
-                    val pref = Preference(context)
-                    pref.title = context.getString(R.string.server_with_name, config.name)
-                    pref.key = "server_$serverId"
-                    pref.order = 10 * serverId
-                    pref.setOnPreferenceClickListener {
-                        parentActivity.openSubScreen(ServerEditorFragment.newInstance(config))
-                        true
-                    }
-                    pref.icon = if (serverId == prefs.getPrimaryServerId() && prefs.getConfiguredServerIds().size > 1) {
-                        ContextCompat.getDrawable(context, R.drawable.ic_star_border_grey_24dp)
-                    } else {
-                        null
-                    }
-                    connCategory.addPreference(pref)
-                    // The pref needs to be attached for doing this
-                    pref.dependency = PrefKeys.DEMO_MODE
-                }
-            }
-        }
-
-        private fun clearImageCache(context: Context) {
-            // Get launch intent for application
-            val restartIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
-            restartIntent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-            // Finish current activity
-            activity?.finish()
-            CacheManager.getInstance(context).clearCache(true)
-            // Start launch activity
-            startActivity(restartIntent)
-        }
-
-        private fun updateNotificationStatusSummaries() {
-            parentActivity.launch {
-                notificationPollingPref?.updateSummaryAndIcon()
-                notificationStatusHint?.apply {
-                    val data = CloudMessagingHelper.getPushNotificationStatus(this.context)
-                    summary = data.message
-                    setIcon(data.icon)
-                }
-            }
-        }
-
-        private fun updateScreenLockStateAndSummary(value: String?) {
-            val pref = findPreference<Preference>(PrefKeys.SCREEN_LOCK) ?: return
-            val km = ContextCompat.getSystemService(pref.context, KeyguardManager::class.java)!!
-            val locked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) km.isDeviceSecure else km.isKeyguardSecure
-            pref.isEnabled = locked
-            pref.summary = getString(when {
-                !locked -> R.string.settings_screen_lock_nolock_summary
-                value == getString(R.string.settings_screen_lock_on_value) -> R.string.settings_screen_lock_on_summary
-                value == getString(R.string.settings_screen_lock_kiosk_value) ->
-                    R.string.settings_screen_lock_kiosk_summary
-                else -> R.string.settings_screen_lock_off_summary
-            })
-        }
-
-        private fun updateRingtonePreferenceSummary(pref: Preference, newValue: Uri?) {
-            if (newValue == null) {
-                pref.setIcon(R.drawable.ic_bell_off_outline_grey_24dp)
-                pref.setSummary(R.string.settings_ringtone_none)
-            } else {
-                pref.setIcon(R.drawable.ic_bell_ring_outline_grey_24dp)
-                val ringtone = RingtoneManager.getRingtone(activity, newValue)
-                pref.summary = try {
-                    ringtone?.getTitle(activity)
-                } catch (e: SecurityException) {
-                    getString(R.string.settings_ringtone_on_external)
-                }
-            }
-        }
-
-        private fun updateVibrationPreferenceIcon(pref: Preference, newValue: String?) {
-            val noVibration = newValue == getString(R.string.settings_notification_vibration_value_off)
-            pref.setIcon(if (noVibration)
-                R.drawable.ic_vibrate_off_grey_24dp
-            else
-                R.drawable.ic_vibration_grey_24dp)
-        }
-
-        @RequiresApi(Build.VERSION_CODES.N)
-        private fun updateTileSummary() {
-            val activeTileCount = (1..AbstractTileService.TILE_COUNT)
-                .mapNotNull { id -> prefs.getTileData(id) }
-                .size
-            val pref = getPreference(PrefKeys.SUBSCREEN_TILE)
-            pref.summary = resources.getQuantityString(R.plurals.tile_active_number, activeTileCount, activeTileCount)
-        }
-
-        private fun isAutomationAppInstalled(): Boolean {
-            val pm = activity?.packageManager ?: return false
-            // These package names must be added to the manifest as well
-            return listOf("net.dinglisch.android.taskerm", "com.twofortyfouram.locale").any { pkg ->
-                pm.isInstalled(pkg)
-            }
-        }
-
-        override fun onActiveConnectionChanged() {
-            // no-op
-        }
-
-        override fun onPrimaryConnectionChanged() {
-            updateNotificationStatusSummaries()
-        }
-
-        override fun onActiveCloudConnectionChanged(connection: CloudConnection?) {
-            // no-op
-        }
-
-        override fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) {
-            updateNotificationStatusSummaries()
-        }
-    }
-
-    class ConfirmationDialogFragment : DialogFragment() {
-        interface Callback {
-            fun onConfirmed(tag: String?)
-        }
-
-        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
-            val args = requireArguments()
-            return AlertDialog.Builder(requireContext())
-                .setMessage(args.getInt("message"))
-                .setPositiveButton(args.getInt("buttontext")) { _, _ ->
-                    val callback = parentFragment as Callback? ?: throw IllegalArgumentException()
-                    callback.onConfirmed(args.getString("tag"))
-                }
-                .setNegativeButton(android.R.string.cancel, null)
-                .create()
-        }
-
-        companion object {
-            fun show(fm: FragmentManager, messageResId: Int, actionButtonTextResId: Int, tag: String) {
-                val f = ConfirmationDialogFragment()
-                f.arguments = bundleOf(
-                    "message" to messageResId,
-                    "buttontext" to actionButtonTextResId,
-                    "tag" to tag
-                )
-                f.show(fm, tag)
-            }
-        }
-    }
-
-    class ConfirmLeaveDialogFragment : DialogFragment() {
-        interface Callback {
-            fun onLeaveAndSave()
-            fun onLeaveAndDiscard()
-        }
-
-        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
-            return AlertDialog.Builder(requireContext())
-                .setTitle(R.string.settings_server_confirm_leave_title)
-                .setMessage(R.string.settings_server_confirm_leave_message)
-                .setPositiveButton(R.string.save) { _, _ -> handleDone(true) }
-                .setNegativeButton(R.string.discard) { _, _ -> handleDone(false) }
-                .setNeutralButton(android.R.string.cancel, null)
-                .create()
-        }
-
-        private fun handleDone(confirmed: Boolean) {
-            val callback = parentFragment as Callback? ?: throw IllegalArgumentException()
-            if (confirmed) {
-                callback.onLeaveAndSave()
-            } else {
-                callback.onLeaveAndDiscard()
-            }
-        }
-    }
-
-    class ServerEditorFragment :
-        AbstractSettingsFragment(),
-        ConfirmationDialogFragment.Callback,
-        ConfirmLeaveDialogFragment.Callback {
-        private lateinit var config: ServerConfiguration
-        private lateinit var initialConfig: ServerConfiguration
-        private var markAsPrimary = false
-
-        override val titleResId: Int get() = R.string.settings_edit_server
-
-        override fun onCreate(savedInstanceState: Bundle?) {
-            config = requireArguments().getParcelable("config", ServerConfiguration::class.java)!!
-            initialConfig = config
-            super.onCreate(savedInstanceState)
-            setHasOptionsMenu(true)
-
-            val backCallback = object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    if (initialConfig != config) {
-                        ConfirmLeaveDialogFragment().show(childFragmentManager, "dialog_confirm_leave")
-                    } else {
-                        isEnabled = false
-                        parentActivity.onBackPressedDispatcher.onBackPressed()
-                    }
-                }
-            }
-
-            parentActivity.onBackPressedDispatcher.addCallback(this, backCallback)
-        }
-
-        override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
-            super.onCreateOptionsMenu(menu, inflater)
-            inflater.inflate(R.menu.server_editor, menu)
-            val deleteItem = menu.findItem(R.id.delete)
-            deleteItem.isVisible = prefs.getConfiguredServerIds().contains(config.id)
-        }
-
-        override fun onOptionsItemSelected(item: MenuItem): Boolean {
-            return when (item.itemId) {
-                R.id.save -> {
-                    saveAndQuit()
-                    true
-                }
-                R.id.delete -> {
-                    ConfirmationDialogFragment.show(
-                        childFragmentManager,
-                        R.string.settings_server_confirm_deletion,
-                        R.string.delete,
-                        "delete_server_confirmation"
-                    )
-                    true
-                }
-                else -> super.onOptionsItemSelected(item)
-            }
-        }
-
-        private fun saveAndQuit() {
-            if (config.name.isEmpty() || (config.localPath == null && config.remotePath == null)) {
-                parentActivity.showSnackbar(
-                    SNACKBAR_TAG_MISSING_PREFS,
-                    R.string.settings_server_at_least_name_and_connection
-                )
-                return
-            }
-            config.saveToPrefs(prefs, secretPrefs)
-            if (markAsPrimary) {
-                prefs.edit {
-                    putPrimaryServerId(config.id)
-                }
-            }
-            parentActivity.invalidateOptionsMenu()
-            parentFragmentManager.popBackStack() // close ourself
-        }
-
-        override fun onConfirmed(tag: String?) = when (tag) {
-            "delete_server_confirmation" -> {
-                config.removeFromPrefs(prefs, secretPrefs)
-                WorkManager.getInstance(preferenceManager.context).apply {
-                    cancelAllWorkByTag(buildWorkerTagForServer(config.id))
-                    pruneWork()
-                }
-                parentFragmentManager.popBackStack() // close ourself
-            }
-            else -> {}
-        }
-
-        override fun onLeaveAndSave() {
-            saveAndQuit()
-        }
-
-        override fun onLeaveAndDiscard() {
-            parentActivity.invalidateOptionsMenu()
-            parentFragmentManager.popBackStack() // close ourself
-        }
-
-        override fun onStart() {
-            super.onStart()
-            updateConnectionSummary("local", config.localPath)
-            updateConnectionSummary("remote", config.remotePath)
-        }
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(R.xml.preferences_server)
-
-            val serverNamePref = getPreference("name") as EditTextPreference
-            serverNamePref.text = config.name
-            serverNamePref.setOnPreferenceChangeListener { _, newValue ->
-                config = ServerConfiguration(
-                    config.id,
-                    newValue as String,
-                    config.localPath,
-                    config.remotePath,
-                    config.sslClientCert,
-                    config.defaultSitemap,
-                    config.wifiSsids,
-                    config.restrictToWifiSsids
-                )
-                parentActivity.invalidateOptionsMenu()
-                true
-            }
-
-            val localConnPref = getPreference("local")
-            localConnPref.setOnPreferenceClickListener {
-                parentActivity.openSubScreen(
-                    ConnectionSettingsFragment.newInstance(
-                        localConnPref.key,
-                        config.localPath,
-                        R.xml.preferences_local_connection,
-                        R.string.settings_openhab_connection,
-                        R.string.settings_openhab_url_summary,
-                        this
-                    )
-                )
-                false
-            }
-
-            val remoteConnPref = getPreference("remote")
-            remoteConnPref.setOnPreferenceClickListener {
-                parentActivity.openSubScreen(
-                    ConnectionSettingsFragment.newInstance(
-                        remoteConnPref.key,
-                        config.remotePath,
-                        R.xml.preferences_remote_connection,
-                        R.string.settings_openhab_alt_connection,
-                        R.string.settings_openhab_alturl_summary,
-                        this
-                    )
-                )
-                false
-            }
-
-            val clientCertPref = getPreference("clientcert") as SslClientCertificatePreference
-            clientCertPref.setOnPreferenceChangeListener { _, newValue ->
-                config = ServerConfiguration(
-                    config.id,
-                    config.name,
-                    config.localPath,
-                    config.remotePath,
-                    newValue as String?,
-                    config.defaultSitemap,
-                    config.wifiSsids,
-                    config.restrictToWifiSsids
-                )
-                true
-            }
-            clientCertPref.setValue(config.sslClientCert)
-
-            val clearDefaultSitemapPref = getPreference(PrefKeys.CLEAR_DEFAULT_SITEMAP)
-            if (config.defaultSitemap?.name.isNullOrEmpty()) {
-                handleNoDefaultSitemap(clearDefaultSitemapPref)
-            } else {
-                clearDefaultSitemapPref.summary = getString(
-                    R.string.settings_current_default_sitemap, config.defaultSitemap?.label.orEmpty()
-                )
-            }
-            clearDefaultSitemapPref.setOnPreferenceClickListener { preference ->
-                preference.sharedPreferences!!.updateDefaultSitemap(null, null, config.id)
-                handleNoDefaultSitemap(preference)
-                parentActivity.resultIntent.putExtra(RESULT_EXTRA_SITEMAP_CLEARED, true)
-                true
-            }
-
-            val wifiSsidPref = getPreference("wifi_ssid") as WifiSsidInputPreference
-            if (prefs.getConfiguredServerIds().isEmpty()) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.PRIMARY_SERVER_PREF)
-                preferenceScreen.removePreferenceRecursively("wifi_ssid")
-            } else {
-                val primaryServerPref = getPreference(PrefKeys.PRIMARY_SERVER_PREF)
-                updatePrimaryServerPrefState(primaryServerPref, config.id == prefs.getPrimaryServerId())
-                primaryServerPref.setOnPreferenceClickListener {
-                    if (prefs.getConfiguredServerIds().contains(config.id)) {
-                        prefs.edit {
-                            putPrimaryServerId(config.id)
-                        }
-                    } else {
-                        markAsPrimary = true
-                    }
-                    updatePrimaryServerPrefState(primaryServerPref, true)
-                    true
-                }
-
-                wifiSsidPref.setValue(
-                    Pair(
-                        config.wifiSsids?.joinToString("\n").orEmpty(),
-                        config.restrictToWifiSsids
-                    )
-                )
-                wifiSsidPref.setOnPreferenceChangeListener { _, newValue ->
-                    @Suppress("UNCHECKED_CAST")
-                    val value = newValue as Pair<String, Boolean>
-                    val ssids = value.first.toWifiSsids()
-                    // Don't restrict if no SSID is set
-                    val restrictToSsids = if (ssids.isNullOrEmpty()) false else value.second
-                    config = ServerConfiguration(
-                        config.id,
-                        config.name,
-                        config.localPath,
-                        config.remotePath,
-                        config.sslClientCert,
-                        config.defaultSitemap,
-                        ssids,
-                        restrictToSsids
-                    )
-                    true
-                }
-            }
-
-            val advancedPrefs = getPreference("advanced_prefs")
-            advancedPrefs.setOnPreferenceClickListener {
-                advancedPrefs.isVisible = false
-                clientCertPref.isVisible = true
-                wifiSsidPref.isVisible = true
-                false
-            }
-        }
-
-        private fun updatePrimaryServerPrefState(pref: Preference, isPrimary: Boolean) {
-            pref.summary = if (isPrimary) {
-                getString(R.string.settings_server_primary_summary_is_primary)
-            } else {
-                val nameOfPrimary = ServerConfiguration.load(prefs, secretPrefs, prefs.getPrimaryServerId())?.name
-                getString(R.string.settings_server_primary_summary_is_not_primary, nameOfPrimary)
-            }
-        }
-
-        private fun handleNoDefaultSitemap(pref: Preference) {
-            pref.isEnabled = false
-            pref.setSummary(R.string.settings_no_default_sitemap)
-        }
-
-        fun onPathChanged(key: String, path: ServerPath) {
-            config = if (key == "local") {
-                ServerConfiguration(
-                    config.id,
-                    config.name,
-                    path,
-                    config.remotePath,
-                    config.sslClientCert,
-                    config.defaultSitemap,
-                    config.wifiSsids,
-                    config.restrictToWifiSsids
-                )
-            } else {
-                ServerConfiguration(
-                    config.id,
-                    config.name,
-                    config.localPath,
-                    path,
-                    config.sslClientCert,
-                    config.defaultSitemap,
-                    config.wifiSsids,
-                    config.restrictToWifiSsids
-                )
-            }
-            parentActivity.invalidateOptionsMenu()
-        }
-
-        private fun updateConnectionSummary(key: String, path: ServerPath?) {
-            fun insecureMessage(beautyUrl: String, reason: Int) =
-                getString(R.string.settings_insecure_connection_summary, beautyUrl, getString(reason))
-
-            val pref = getPreference(key)
-            val beautyUrl = beautifyUrl(path?.url.orEmpty())
-            pref.summary = when {
-                path == null || path.url.isEmpty() ->
-                    getString(R.string.info_not_set)
-                path.url.toHttpUrlOrNull()?.isHttps == false ->
-                    insecureMessage(beautyUrl, R.string.settings_insecure_connection_no_https)
-                !path.hasAuthentication() && config.sslClientCert == null ->
-                    insecureMessage(beautyUrl, R.string.settings_insecure_connection_no_auth)
-                isWeakPassword(path.password) ->
-                    insecureMessage(beautyUrl, R.string.settings_openhab_password_summary_weak)
-                else ->
-                    getString(R.string.settings_connection_summary, beautyUrl)
-            }
-        }
-
-        companion object {
-            fun newInstance(config: ServerConfiguration): ServerEditorFragment {
-                val f = ServerEditorFragment()
-                f.arguments = bundleOf("config" to config)
-                return f
-            }
-
-            @VisibleForTesting fun beautifyUrl(url: String): String {
-                val host = url.toHttpUrlOrNull()?.host ?: url
-                return if (HttpClient.isMyOpenhab(host)) "myopenHAB" else host
-            }
-        }
-    }
-
-
-    internal class ConnectionSettingsFragment : AbstractSettingsFragment() {
-        override val titleResId: Int @StringRes get() = requireArguments().getInt("title")
-
-        private lateinit var urlPreference: EditTextPreference
-        private lateinit var userNamePreference: EditTextPreference
-        private lateinit var passwordPreference: EditTextPreference
-        private lateinit var parent: ServerEditorFragment
-        private lateinit var path: ServerPath
-
-        override fun onAttach(context: Context) {
-            super.onAttach(context)
-            parent = parentFragmentManager.getFragment(requireArguments(), "parent") as ServerEditorFragment
-        }
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(requireArguments().getInt("prefs"))
-
-            path = requireArguments().getParcelable("path", ServerPath::class.java)
-                ?: ServerPath("", null, null)
-
-            urlPreference = initEditor("url", path.url, R.drawable.ic_earth_grey_24dp) { value ->
-                val actualValue = if (!value.isNullOrEmpty()) value else getString(R.string.info_not_set)
-                getString(requireArguments().getInt("urlsummary"), actualValue)
-            }
-
-            userNamePreference = initEditor(
-                "username",
-                path.userName,
-                R.drawable.ic_person_outline_grey_24dp
-            ) { value ->
-                if (!value.isNullOrEmpty()) value else getString(R.string.info_not_set)
-            }
-            passwordPreference = initEditor(
-                "password",
-                path.password,
-                R.drawable.ic_shield_key_outline_grey_24dp
-            ) { value ->
-                getString(when {
-                    value.isNullOrEmpty() -> R.string.info_not_set
-                    isWeakPassword(value) -> R.string.settings_openhab_password_summary_weak
-                    else -> R.string.settings_openhab_password_summary_strong
-                })
-            }
-
-            updateIconColors(urlPreference.text, userNamePreference.text, passwordPreference.text)
-        }
-
-        private fun initEditor(
-            key: String,
-            initialValue: String?,
-            @DrawableRes iconResId: Int,
-            summaryGenerator: (value: String?) -> CharSequence
-        ): EditTextPreference {
-            val preference = preferenceScreen.findPreference<EditTextPreference>(key)!!
-            preference.icon = DrawableCompat.wrap(ContextCompat.getDrawable(preference.context, iconResId)!!)
-            preference.text = initialValue
-            preference.setOnPreferenceChangeListener { pref, newValue ->
-                val url = if (pref === urlPreference) newValue as String else urlPreference.text
-                val userName = if (pref === userNamePreference) newValue as String else userNamePreference.text
-                val password = if (pref === passwordPreference) newValue as String else passwordPreference.text
-
-                updateIconColors(url, userName, password)
-                pref.summary = summaryGenerator(newValue as String)
-
-                if (url != null) {
-                    val path = ServerPath(url, userName, password)
-                    parent.onPathChanged(requireArguments().getString("key", ""), path)
-                }
-                true
-            }
-            preference.summary = summaryGenerator(initialValue)
-            return preference
-        }
-
-        private fun updateIconColors(url: String?, userName: String?, password: String?) {
-            updateIconColor(urlPreference) { when {
-                url?.toHttpUrlOrNull()?.isHttps == true -> R.color.pref_icon_green
-                !url.isNullOrEmpty() -> R.color.pref_icon_red
-                else -> null
-            } }
-            updateIconColor(userNamePreference) { when {
-                url.isNullOrEmpty() -> null
-                userName.isNullOrEmpty() -> R.color.pref_icon_red
-                else -> R.color.pref_icon_green
-            } }
-            updateIconColor(passwordPreference) { when {
-                url.isNullOrEmpty() -> null
-                password.isNullOrEmpty() -> R.color.pref_icon_red
-                isWeakPassword(password) -> R.color.pref_icon_orange
-                else -> R.color.pref_icon_green
-            } }
-        }
-
-        private fun updateIconColor(pref: Preference, colorGenerator: () -> Int?) {
-            pref.icon?.let { icon ->
-                val colorResId = colorGenerator()
-                if (colorResId != null) {
-                    DrawableCompat.setTint(icon, ContextCompat.getColor(pref.context, colorResId))
-                } else {
-                    DrawableCompat.setTintList(icon, null)
-                }
-            }
-        }
-
-        companion object {
-            fun newInstance(
-                key: String,
-                serverPath: ServerPath?,
-                prefsResId: Int,
-                titleResId: Int,
-                urlSummaryResId: Int,
-                parent: ServerEditorFragment
-            ): ConnectionSettingsFragment {
-                val f = ConnectionSettingsFragment()
-                val args = bundleOf(
-                    "key" to key,
-                    "path" to serverPath,
-                    "prefs" to prefsResId,
-                    "title" to titleResId,
-                    "urlsummary" to urlSummaryResId
-                )
-                parent.parentFragmentManager.putFragment(args, "parent", parent)
-                f.arguments = args
-                return f
-            }
-        }
-    }
-
-    internal class DrawerEntriesMenuFragment : AbstractSettingsFragment() {
-        override val titleResId: Int @StringRes get() = R.string.drawer_entries
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(R.xml.preferences_drawer_entries)
-
-            val showSitemapInDrawerPref = getPreference(PrefKeys.SHOW_SITEMAPS_IN_DRAWER)
-
-            showSitemapInDrawerPref.setOnPreferenceChangeListener { _, _ ->
-                parentActivity.resultIntent.putExtra(RESULT_EXTRA_SITEMAP_DRAWER_CHANGED, true)
-                true
-            }
-
-            if (NfcAdapter.getDefaultAdapter(requireContext()) == null && !Util.isEmulator()) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.DRAWER_ENTRY_NFC)
-            }
-        }
-    }
-
-    internal class SendDeviceInfoSettingsFragment :
-        AbstractSettingsFragment(),
-        SharedPreferences.OnSharedPreferenceChangeListener {
-        override val titleResId: Int @StringRes get() = R.string.send_device_info_to_server_short
-        private lateinit var phoneStatePref: ItemUpdatingPreference
-        private lateinit var wifiSsidPref: ItemUpdatingPreference
-        private lateinit var bluetoothPref: ItemUpdatingPreference
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(R.xml.preferences_device_information)
-
-            val prefixHint = getPreference(PrefKeys.DEV_ID_PREFIX_BG_TASKS)
-            val foregroundServicePref = getPreference(PrefKeys.SEND_DEVICE_INFO_FOREGROUND_SERVICE)
-            phoneStatePref = getPreference(PrefKeys.SEND_PHONE_STATE) as ItemUpdatingPreference
-            wifiSsidPref = getPreference(PrefKeys.SEND_WIFI_SSID) as ItemUpdatingPreference
-            bluetoothPref = getPreference(PrefKeys.SEND_BLUETOOTH_DEVICES) as ItemUpdatingPreference
-
-            phoneStatePref.setOnPreferenceChangeListener { _, newValue ->
-                requestPermissionIfEnabled(
-                    newValue,
-                    BackgroundTasksManager.getRequiredPermissionsForTask(PrefKeys.SEND_PHONE_STATE),
-                    PERMISSIONS_REQUEST_FOR_CALL_STATE
-                )
-                true
-            }
-
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                wifiSsidPref.setSummaryOn(getString(R.string.settings_wifi_ssid_summary_on_location_on))
-            }
-            wifiSsidPref.setOnPreferenceChangeListener { _, newValue ->
-                requestPermissionIfEnabled(
-                    newValue,
-                    BackgroundTasksManager.getRequiredPermissionsForTask(PrefKeys.SEND_WIFI_SSID),
-                    PERMISSIONS_REQUEST_FOR_WIFI_NAME
-                )
-                true
-            }
-
-            bluetoothPref.setOnPreferenceChangeListener { _, newValue ->
-                requestPermissionIfEnabled(
-                    newValue,
-                    BackgroundTasksManager.getRequiredPermissionsForTask(PrefKeys.SEND_BLUETOOTH_DEVICES),
-                    PERMISSIONS_REQUEST_FOR_BLUETOOTH_DEVICES
-                )
-                true
-            }
-
-            if (activity?.packageManager?.isInstalled("nodomain.freeyourgadget.gadgetbridge") == false) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.SEND_GADGETBRIDGE)
-            }
-
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-                preferenceScreen.removePreferenceRecursively(PrefKeys.SEND_DND_MODE)
-                preferenceScreen.removePreferenceRecursively(PrefKeys.SEND_DEVICE_INFO_FOREGROUND_SERVICE)
-            } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
-                foregroundServicePref.setSummary(R.string.send_device_info_foreground_service_summary_pre_o)
-            }
-
-            foregroundServicePref.setOnPreferenceChangeListener { preference, newValue ->
-                EventListenerService.startOrStopService(preference.context, newValue as Boolean)
-                true
-            }
-
-            BackgroundTasksManager.KNOWN_KEYS.forEach { key ->
-                findPreference<ItemUpdatingPreference>(key)?.startObserving(this)
-            }
-
-            prefs.registerOnSharedPreferenceChangeListener(this)
-
-            val prefix = prefs.getPrefixForBgTasks()
-            prefixHint.summary = if (prefix.isEmpty()) {
-                prefixHint.context.getString(R.string.send_device_info_item_prefix_summary_not_set)
-            } else {
-                prefixHint.context.getString(R.string.send_device_info_item_prefix_summary, prefix)
-            }
-        }
-
-        override fun onDetach() {
-            prefs.unregisterOnSharedPreferenceChangeListener(this)
-            super.onDetach()
-        }
-
-        override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
-            if (key in BackgroundTasksManager.KNOWN_PERIODIC_KEYS) {
-                EventListenerService.startOrStopService(requireContext())
-            }
-        }
-
-        private fun requestPermissionIfEnabled(
-            newValue: Any?,
-            permissions: Array<String>?,
-            requestCode: Int
-        ) {
-            @Suppress("UNCHECKED_CAST")
-            if ((newValue as Pair<Boolean, String>).first) {
-                parentActivity.requestPermissionsIfRequired(permissions, requestCode)
-            }
-        }
-
-        override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
-            val context = phoneStatePref.context
-
-            when (requestCode) {
-                PERMISSIONS_REQUEST_FOR_CALL_STATE -> {
-                    if (grantResults.firstOrNull { it != PackageManager.PERMISSION_GRANTED } != null) {
-                        parentActivity.showSnackbar(
-                            SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_PHONE,
-                            R.string.settings_phone_state_permission_denied
-                        )
-                        phoneStatePref.setValue(checked = false)
-                    } else {
-                        BackgroundTasksManager.scheduleWorker(context, PrefKeys.SEND_PHONE_STATE, true)
-                    }
-                }
-                PERMISSIONS_REQUEST_FOR_WIFI_NAME -> {
-                    if (grantResults.firstOrNull { it != PackageManager.PERMISSION_GRANTED } != null) {
-                        parentActivity.showSnackbar(
-                            SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_WIFI,
-                            R.string.settings_wifi_ssid_permission_denied
-                        )
-                        wifiSsidPref.setValue(checked = false)
-                    } else {
-                        BackgroundTasksManager.scheduleWorker(context, PrefKeys.SEND_WIFI_SSID, true)
-                    }
-                }
-                PERMISSIONS_REQUEST_FOR_BLUETOOTH_DEVICES -> {
-                    if (grantResults.firstOrNull { it != PackageManager.PERMISSION_GRANTED } != null) {
-                        parentActivity.showSnackbar(
-                            SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_BLUETOOTH,
-                            R.string.settings_bluetooth_devices_permission_denied
-                        )
-                        bluetoothPref.setValue(checked = false)
-                    } else {
-                        BackgroundTasksManager.scheduleWorker(context, PrefKeys.SEND_BLUETOOTH_DEVICES, true)
-                    }
-                }
-            }
-        }
-    }
-
-    @RequiresApi(Build.VERSION_CODES.N)
-    internal class TileOverviewFragment : AbstractSettingsFragment() {
-        override val titleResId: Int @StringRes get() = R.string.tiles_for_quick_settings
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(R.xml.preferences_tile_overview)
-            for (tileId in 1..AbstractTileService.TILE_COUNT) {
-                val tilePref = Preference(preferenceManager.context).apply {
-                    key = "tile_$tileId"
-                    title = getString(R.string.tile_number, tileId)
-                    isPersistent = false
-                }
-                tilePref.setOnPreferenceClickListener {
-                    parentActivity.openSubScreen(TileSettingsFragment.newInstance(tileId))
-                    false
-                }
-                preferenceScreen.addPreference(tilePref)
-            }
-        }
-
-        override fun onResume() {
-            super.onResume()
-            preferenceScreen.forEachIndexed { index, preference ->
-                // Index 0 is the hint
-                if (index != 0) {
-                    val data = prefs.getTileData(index)
-                    val context = preference.context
-                    preference.summary = data?.tileLabel ?: getString(R.string.tile_disabled)
-                    preference.icon = if (data == null) {
-                        null
-                    } else {
-                        ContextCompat.getDrawable(context, AbstractTileService.getIconRes(context, data.icon))?.apply {
-                            mutate()
-                            setTint(context.getColor(R.color.pref_icon_grey))
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    @RequiresApi(Build.VERSION_CODES.N)
-    internal class TileSettingsFragment : AbstractSettingsFragment(), ConfirmLeaveDialogFragment.Callback {
-        override val titleResId: Int @StringRes get() = R.string.tile
-        private var tileId = 0
-
-        private lateinit var enabledPref: SwitchPreferenceCompat
-        private lateinit var itemAndStatePref: ItemAndStatePreference
-        private lateinit var namePref: CustomInputTypePreference
-        private lateinit var iconPref: ListPreference
-        private lateinit var requireUnlockPref: SwitchPreferenceCompat
-        private var itemAndStatePrefCallback = registerForActivityResult(
-            ActivityResultContracts.StartActivityForResult()
-        ) { result ->
-            Log.d(TAG, "itemAndStatePrefCallback: $result")
-            val data = result.data ?: return@registerForActivityResult
-            itemAndStatePref.item = data.getStringExtra("item")
-            itemAndStatePref.label = data.getStringExtra("label")
-            itemAndStatePref.state = data.getStringExtra("state")
-            itemAndStatePref.mappedState = data.getStringExtra("mappedState")
-            val itemTags = data.extras?.getParcelableArray("tags", Item.Tag::class.java)
-            updateItemAndStatePrefSummary()
-
-            if (namePref.text.isNullOrEmpty()) {
-                namePref.text = itemAndStatePref.label
-            }
-            if (iconPref.value == null || iconPref.value == getString(R.string.tile_icon_openhab_value)) {
-                val selectedIcon = data.getStringExtra("icon") ?: "openhab_icon"
-                val preSelectIcon = if (selectedIcon.startsWith("parents")) {
-                    R.string.tile_icon_people_value
-                } else if (selectedIcon.startsWith("boy") || selectedIcon.startsWith("girl")) {
-                    R.string.tile_icon_child_value
-                } else if (selectedIcon.startsWith("baby")) {
-                    R.string.tile_icon_baby_value
-                } else if (selectedIcon.startsWith("man")) {
-                    R.string.tile_icon_man_value
-                } else if (selectedIcon.startsWith("women")) {
-                    R.string.tile_icon_woman_value
-                } else {
-                    when (selectedIcon) {
-                        "screen" -> R.string.tile_icon_tv_value
-                        "lightbulb", "light", "slider" -> R.string.tile_icon_bulb_value
-                        "lock" -> R.string.tile_icon_lock_value
-                        "time" -> R.string.tile_icon_clock_value
-                        "house", "presence", "group" -> R.string.tile_icon_house_value
-                        "microphone", "recorder" -> R.string.tile_icon_microphone_value
-                        "colorpicker", "colorlight", "colorwheel", "rbg" -> R.string.tile_icon_color_palette_value
-                        "battery", "batterylevel", "lowbattery" -> R.string.tile_icon_battery_value
-                        "zoom" -> R.string.tile_icon_magnifier_value
-                        "garden" -> R.string.tile_icon_tree_value
-                        "network" -> R.string.tile_icon_wifi_value
-                        "shield" -> R.string.tile_icon_shield_value
-                        "fan", "fan_box", "fan_ceiling" -> R.string.tile_icon_fan_value
-                        "bedroom", "bedroom_blue", "bedroom_orange", "bedroom_red" -> R.string.tile_icon_bed_value
-                        "settings" -> R.string.tile_icon_settings_value
-                        "bath", "toilet" -> R.string.tile_icon_bath_value
-                        "blinds", "rollershutter" -> R.string.tile_icon_roller_shutter_value
-                        "camera" -> R.string.tile_icon_camera_value
-                        "wallswitch" -> R.string.tile_icon_light_switch_value
-                        "garage", "garagedoor", "garage_detached", "garage_detached_selected" ->
-                            R.string.tile_icon_garage_value
-                        "switch" -> R.string.tile_icon_switch_value
-                        "text" -> R.string.tile_icon_text_value
-                        "sofa" -> R.string.tile_icon_sofa_value
-                        else -> when {
-                            itemTags?.contains(Item.Tag.Light) == true -> R.string.tile_icon_bulb_value
-                            itemTags?.contains(Item.Tag.Blinds) == true -> R.string.tile_icon_roller_shutter_value
-                            itemTags?.contains(Item.Tag.Switch) == true -> R.string.tile_icon_switch_value
-                            else -> R.string.tile_icon_openhab_value
-                        }
-                    }
-                }
-                iconPref.value = getString(preSelectIcon)
-                updateIconPrefIcon()
-            }
-        }
-
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            tileId = arguments?.getInt("id") ?: throw AssertionError("No tile id specified")
-            setHasOptionsMenu(true)
-
-            setDataFromPrefs()
-
-            val backCallback = object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    if (prefs.getTileData(tileId) != getCurrentPrefsAsTileData()) {
-                        ConfirmLeaveDialogFragment().show(childFragmentManager, "dialog_confirm_leave")
-                    } else {
-                        isEnabled = false
-                        parentActivity.onBackPressedDispatcher.onBackPressed()
-                    }
-                }
-            }
-
-            parentActivity.onBackPressedDispatcher.addCallback(this, backCallback)
-        }
-
-        override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
-            super.onCreateOptionsMenu(menu, inflater)
-            inflater.inflate(R.menu.prefs_save, menu)
-        }
-
-        override fun onOptionsItemSelected(item: MenuItem): Boolean {
-            return when (item.itemId) {
-                R.id.save -> {
-                    onLeaveAndSave()
-                    true
-                }
-                else -> super.onOptionsItemSelected(item)
-            }
-        }
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(R.xml.preferences_tile)
-            enabledPref = findPreference("tile_show")!!
-            itemAndStatePref = findPreference("tile_item_and_action")!!
-            namePref = findPreference("tile_name")!!
-            iconPref = findPreference("tile_icon")!!
-            requireUnlockPref = findPreference("tile_require_unlock")!!
-
-            namePref.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
-            itemAndStatePref.setOnPreferenceClickListener {
-                val intent = Intent(it.context, BasicItemPickerActivity::class.java)
-                intent.putExtra("item", itemAndStatePref.item)
-                itemAndStatePrefCallback.launch(intent)
-                true
-            }
-        }
-
-        private fun updateItemAndStatePrefSummary() {
-            itemAndStatePref.summary = if (itemAndStatePref.label == null) {
-                itemAndStatePref.context.getString(R.string.info_not_set)
-            } else {
-                "${itemAndStatePref.label} (${itemAndStatePref.item}): ${itemAndStatePref.mappedState}"
-            }
-        }
-
-        private fun updateIconPrefIcon(newIcon: String = iconPref.value) {
-            val context = iconPref.context
-            iconPref.icon =
-                ContextCompat.getDrawable(context, AbstractTileService.getIconRes(context, newIcon))?.apply {
-                    mutate()
-                    setTint(context.getColor(R.color.pref_icon_grey))
-                }
-        }
-
-        private fun getCurrentPrefsAsTileData(): TileData? {
-            return if (enabledPref.isChecked) {
-                TileData(
-                    item = itemAndStatePref.item.orEmpty(),
-                    state = itemAndStatePref.state.orEmpty(),
-                    label = itemAndStatePref.label.orEmpty(),
-                    tileLabel = namePref.text.orEmpty(),
-                    mappedState = itemAndStatePref.mappedState.orEmpty(),
-                    icon = iconPref.value.orEmpty(),
-                    requireUnlock = requireUnlockPref.isChecked
-                )
-            } else {
-                null
-            }
-        }
-
-        private fun setDataFromPrefs() {
-            val data = prefs.getTileData(tileId)
-            enabledPref.isChecked = data != null
-            if (data != null) {
-                itemAndStatePref.item = data.item
-                itemAndStatePref.label = data.label
-                itemAndStatePref.state = data.state
-                itemAndStatePref.mappedState = data.mappedState
-                namePref.text = data.tileLabel
-                iconPref.value = data.icon
-                requireUnlockPref.isChecked = data.requireUnlock
-            }
-            iconPref.setOnPreferenceChangeListener { _, newValue ->
-                updateIconPrefIcon(newValue as String)
-                true
-            }
-            updateIconPrefIcon()
-            updateItemAndStatePrefSummary()
-        }
-
-        override fun onLeaveAndSave() {
-            Log.d(TAG, "Save tile $tileId")
-            val context = preferenceManager.context
-            val currentData = getCurrentPrefsAsTileData()
-            if (currentData != null && !currentData.isValid()) {
-                parentActivity.showSnackbar(
-                    SNACKBAR_TAG_MISSING_PREFS,
-                    R.string.error_missing_prefs,
-                    Snackbar.LENGTH_LONG
-                )
-                return
-            }
-
-            prefs.edit {
-                putTileData(tileId, currentData)
-            }
-            AbstractTileService.requestTileUpdate(context, tileId)
-
-            parentActivity.onBackPressedDispatcher.onBackPressed()
-        }
-
-        override fun onLeaveAndDiscard() {
-            setDataFromPrefs()
-            parentActivity.onBackPressedDispatcher.onBackPressed()
-        }
-
-        companion object {
-            fun newInstance(id: Int): TileSettingsFragment {
-                val f = TileSettingsFragment()
-                val args = bundleOf("id" to id)
-                f.arguments = args
-                return f
-            }
-        }
-    }
-
-    internal class WidgetSettingsFragment : AbstractSettingsFragment(), ConfirmLeaveDialogFragment.Callback {
-        override val titleResId: Int @StringRes get() = R.string.item_update_widget
-        private var widgetId: Int = AppWidgetManager.INVALID_APPWIDGET_ID
-
-        private lateinit var itemAndStatePref: ItemAndStatePreference
-        private lateinit var namePref: CustomInputTypePreference
-        private lateinit var showStatePref: SwitchPreference
-        private lateinit var themePref: ListPreference
-        private var itemAndStatePrefCallback = registerForActivityResult(
-            ActivityResultContracts.StartActivityForResult()
-        ) { result ->
-            Log.d(TAG, "itemAndStatePrefCallback: $result")
-            val data = result.data ?: return@registerForActivityResult
-            itemAndStatePref.item = data.getStringExtra("item")
-            val label = if (data.getStringExtra("label").isNullOrEmpty()) {
-                itemAndStatePref.item
-            } else {
-                data.getStringExtra("label")
-            }
-            itemAndStatePref.label = label
-            itemAndStatePref.state = data.getStringExtra("state")
-            itemAndStatePref.mappedState = data.getStringExtra("mappedState")
-            itemAndStatePref.icon = data.getStringExtra("icon")
-            updateItemAndStatePrefSummary()
-
-            if (namePref.text.isNullOrEmpty()) {
-                namePref.text = preferenceManager.context.getString(
-                    R.string.item_update_widget_text,
-                    itemAndStatePref.label,
-                    itemAndStatePref.mappedState
-                )
-            }
-        }
-
-        override fun onCreate(savedInstanceState: Bundle?) {
-            super.onCreate(savedInstanceState)
-            setHasOptionsMenu(true)
-            activity?.setResult(RESULT_CANCELED)
-
-            widgetId = requireArguments().getInt("id", AppWidgetManager.INVALID_APPWIDGET_ID)
-            if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
-                throw IllegalArgumentException("$TAG called with INVALID_APPWIDGET_ID")
-            }
-
-            setDataFromPrefs()
-
-            val backCallback = object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    val oldData = ItemUpdateWidget.getInfoForWidget(requireContext(), widgetId)
-                    val newData = getCurrentPrefsAsWidgetData()
-                    if (oldData != newData) {
-                        ConfirmLeaveDialogFragment().show(childFragmentManager, "dialog_confirm_leave")
-                    } else {
-                        isEnabled = false
-                        parentActivity.onBackPressedDispatcher.onBackPressed()
-                    }
-                }
-            }
-
-            parentActivity.onBackPressedDispatcher.addCallback(this, backCallback)
-        }
-
-        override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
-            super.onCreateOptionsMenu(menu, inflater)
-            inflater.inflate(R.menu.prefs_save, menu)
-        }
-
-        override fun onOptionsItemSelected(item: MenuItem): Boolean {
-            return when (item.itemId) {
-                R.id.save -> {
-                    onLeaveAndSave()
-                    true
-                }
-                else -> super.onOptionsItemSelected(item)
-            }
-        }
-
-        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-            addPreferencesFromResource(R.xml.preferences_homescreen_widget)
-            itemAndStatePref = findPreference("widget_item_and_action")!!
-            namePref = findPreference("widget_name")!!
-            showStatePref = findPreference("show_state")!!
-            themePref = findPreference("widget_theme")!!
-
-            namePref.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
-            itemAndStatePref.setOnPreferenceClickListener {
-                val intent = Intent(it.context, BasicItemPickerActivity::class.java)
-                intent.putExtra("item", itemAndStatePref.item)
-                intent.putExtra("show_no_command", true)
-                itemAndStatePrefCallback.launch(intent)
-                true
-            }
-        }
-
-        private fun updateItemAndStatePrefSummary() {
-            itemAndStatePref.summary = if (itemAndStatePref.item.isNullOrEmpty()) {
-                itemAndStatePref.context.getString(R.string.info_not_set)
-            } else {
-                "${itemAndStatePref.label} (${itemAndStatePref.item}): ${itemAndStatePref.mappedState}"
-            }
-        }
-
-        private fun getCurrentPrefsAsWidgetData(): ItemUpdateWidget.ItemUpdateWidgetData {
-            return ItemUpdateWidget.ItemUpdateWidgetData(
-                item = itemAndStatePref.item.orEmpty(),
-                command = itemAndStatePref.state,
-                label = itemAndStatePref.label.orEmpty(),
-                widgetLabel = namePref.text.orEmpty(),
-                mappedState = itemAndStatePref.mappedState.orEmpty(),
-                icon = itemAndStatePref.icon.toOH2IconResource(),
-                theme = themePref.value,
-                showState = showStatePref.isChecked
-            )
-        }
-
-        private fun setDataFromPrefs() {
-            val data = ItemUpdateWidget.getInfoForWidget(requireContext(), widgetId)
-
-            itemAndStatePref.item = data.item
-            itemAndStatePref.label = data.label
-            itemAndStatePref.state = data.command
-            itemAndStatePref.mappedState = data.mappedState
-            itemAndStatePref.icon = data.icon?.icon
-            namePref.text = data.widgetLabel
-            showStatePref.isChecked = data.showState
-            themePref.value = data.theme
-
-            updateItemAndStatePrefSummary()
-        }
-
-        override fun onLeaveAndSave() {
-            Log.d(TAG, "Save widget $widgetId")
-            val context = preferenceManager.context
-            val newData = getCurrentPrefsAsWidgetData()
-            if (!newData.isValid()) {
-                parentActivity.showSnackbar(
-                    SNACKBAR_TAG_MISSING_PREFS,
-                    R.string.error_missing_prefs,
-                    Snackbar.LENGTH_LONG
-                )
-                return
-            }
-
-            val oldData = ItemUpdateWidget.getInfoForWidget(context, widgetId)
-            if (oldData.icon != newData.icon) {
-                CacheManager.getInstance(context).removeWidgetIcon(widgetId)
-            }
-
-            ItemUpdateWidget.saveInfoForWidget(context, newData, widgetId)
-            prefs.edit {
-                putString(PrefKeys.LAST_WIDGET_THEME, newData.theme)
-            }
-
-            BackgroundTasksManager.schedulePeriodicTrigger(context, false)
-
-            val updateIntent = Intent(context, ItemUpdateWidget::class.java).apply {
-                action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
-                putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(widgetId))
-            }
-            context.sendBroadcast(updateIntent)
-
-            val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
-            activity?.setResult(RESULT_OK, resultValue)
-
-            parentActivity.onBackPressedDispatcher.onBackPressed()
-        }
-
-        override fun onLeaveAndDiscard() {
-            setDataFromPrefs()
-            parentActivity.onBackPressedDispatcher.onBackPressed()
-        }
-
-        companion object {
-            private val TAG = WidgetSettingsFragment::class.java.simpleName
-
-            fun newInstance(id: Int): WidgetSettingsFragment {
-                val f = WidgetSettingsFragment()
-                val args = bundleOf("id" to id)
-                f.arguments = args
-                return f
-            }
-        }
-    }
-
-    companion object {
-        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"
-        const val START_EXTRA_SERVER_PROPERTIES = "server_properties"
-        const val ITEM_UPDATE_WIDGET_ITEM = "item"
-        const val ITEM_UPDATE_WIDGET_COMMAND = "state"
-        const val ITEM_UPDATE_WIDGET_LABEL = "label"
-        const val ITEM_UPDATE_WIDGET_WIDGET_LABEL = "widgetLabel"
-        const val ITEM_UPDATE_WIDGET_MAPPED_STATE = "mappedState"
-        const val ITEM_UPDATE_WIDGET_ICON = "icon"
-        const val ITEM_UPDATE_WIDGET_THEME = "theme"
-        const val ITEM_UPDATE_WIDGET_SHOW_STATE = "show_state"
-        private const val STATE_KEY_RESULT = "result"
-        private const val PERMISSIONS_REQUEST_FOR_CALL_STATE = 0
-        private const val PERMISSIONS_REQUEST_FOR_WIFI_NAME = 1
-        private const val PERMISSIONS_REQUEST_FOR_BLUETOOTH_DEVICES = 2
-
-        internal const val SNACKBAR_TAG_CLIENT_SSL_NOT_SUPPORTED = "clientSslNotSupported"
-        internal const val SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_PHONE = "bgTasksPermissionDeclinedPhone"
-        internal const val SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_WIFI = "bgTasksPermissionDeclinedWifi"
-        internal const val SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_BLUETOOTH = "bgTasksPermissionDeclinedBluetooth"
-        internal const val SNACKBAR_TAG_BG_TASKS_MISSING_PERMISSION_LOCATION = "bgTasksMissingPermissionLocation"
-        internal const val SNACKBAR_TAG_MISSING_PREFS = "missingPrefs"
-
-        private val TAG = PreferencesActivity::class.java.simpleName
-    }
-}
-
-interface CustomDialogPreference {
-    fun createDialog(): DialogFragment
-}
-
-data class PushNotificationStatus(
-    val message: String,
-    @DrawableRes val icon: Int,
-    val notifyUser: Boolean
-)
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt
index 62b52a4a2e..0ac7d4ebd5 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt
@@ -53,7 +53,7 @@ import org.openhab.habdroid.model.WebViewUi
 import org.openhab.habdroid.model.Widget
 import org.openhab.habdroid.ui.CloudNotificationListFragment
 import org.openhab.habdroid.ui.MainActivity
-import org.openhab.habdroid.ui.PreferencesActivity
+import org.openhab.habdroid.ui.preference.PreferencesActivity
 import org.openhab.habdroid.ui.WidgetListFragment
 import org.openhab.habdroid.ui.activity.AbstractWebViewFragment.Companion.KEY_IS_STACK_ROOT
 import org.openhab.habdroid.ui.activity.AbstractWebViewFragment.Companion.KEY_SUBPAGE
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt
index 269a5306f0..129425f62f 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt
@@ -45,7 +45,7 @@ import org.openhab.habdroid.model.IconFormat
 import org.openhab.habdroid.model.IconResource
 import org.openhab.habdroid.model.getIconResource
 import org.openhab.habdroid.model.putIconResource
-import org.openhab.habdroid.ui.PreferencesActivity
+import org.openhab.habdroid.ui.preference.PreferencesActivity
 import org.openhab.habdroid.ui.duplicate
 import org.openhab.habdroid.util.CacheManager
 import org.openhab.habdroid.util.HttpClient
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
new file mode 100644
index 0000000000..92d9d48309
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PreferencesActivity.kt
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2010-2022 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.app.Dialog
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.service.quicksettings.TileService
+import android.view.MenuItem
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import androidx.appcompat.app.AlertDialog
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.commit
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import java.util.BitSet
+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.MainSettingsFragment
+import org.openhab.habdroid.ui.preference.fragments.TileOverviewFragment
+import org.openhab.habdroid.ui.preference.fragments.TileSettingsFragment
+import org.openhab.habdroid.ui.preference.fragments.WidgetSettingsFragment
+import org.openhab.habdroid.util.getSecretPrefs
+
+/**
+ * This is a class to provide preferences activity for application.
+ */
+class PreferencesActivity : AbstractBaseActivity() {
+    private lateinit var resultIntent: Intent
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(R.layout.activity_prefs)
+
+        setSupportActionBar(findViewById(R.id.openhab_toolbar))
+        supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+        if (savedInstanceState == null) {
+            resultIntent = Intent()
+            val fragment = when {
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
+                    intent.action == TileService.ACTION_QS_TILE_PREFERENCES -> {
+                    val tile = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+                    val tileId: Int = tile?.className?.let { AbstractTileService.getIdFromClassName(it) } ?: 0
+                    if (tileId > 0) {
+                        TileSettingsFragment.newInstance(tileId)
+                    } else {
+                        TileOverviewFragment()
+                    }
+                }
+                intent.action == AppWidgetManager.ACTION_APPWIDGET_CONFIGURE -> {
+                    val id = intent?.extras?.getInt(
+                        AppWidgetManager.EXTRA_APPWIDGET_ID,
+                        AppWidgetManager.INVALID_APPWIDGET_ID
+                    ) ?: AppWidgetManager.INVALID_APPWIDGET_ID
+                    WidgetSettingsFragment.newInstance(id)
+                }
+                else -> {
+                    MainSettingsFragment()
+                }
+            }
+            supportFragmentManager.commit {
+                add(R.id.activity_content, fragment)
+            }
+        } else {
+            resultIntent = savedInstanceState.getParcelable(STATE_KEY_RESULT, Intent::class.java) ?: Intent()
+        }
+        setResult(RESULT_OK, resultIntent)
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        outState.putParcelable(STATE_KEY_RESULT, resultIntent)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        if (isFinishing) {
+            return true
+        }
+        return when (item.itemId) {
+            android.R.id.home -> {
+                onBackPressedDispatcher.onBackPressed()
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+
+    fun handleThemeChange() {
+        addResultFlag(RESULT_EXTRA_THEME_CHANGED)
+        recreate()
+    }
+
+    fun addResultFlag(name: String) {
+        resultIntent.putExtra(name, true)
+    }
+
+    fun openSubScreen(subScreenFragment: AbstractSettingsFragment) {
+        supportFragmentManager.commit {
+            replace(R.id.activity_content, subScreenFragment)
+            addToBackStack(null)
+        }
+    }
+
+    @VisibleForTesting
+    abstract class AbstractSettingsFragment : PreferenceFragmentCompat() {
+        @get:StringRes
+        protected abstract val titleResId: Int
+
+        protected val parentActivity get() = activity as PreferencesActivity
+        protected val prefs get() = preferenceScreen.sharedPreferences!!
+        protected val secretPrefs get() = requireContext().getSecretPrefs()
+
+        override fun onStart() {
+            super.onStart()
+            parentActivity.supportActionBar?.setTitle(titleResId)
+        }
+
+        override fun onDisplayPreferenceDialog(preference: Preference) {
+            val showDialog: (DialogFragment) -> Unit = { fragment ->
+                @Suppress("DEPRECATION") // TODO: Replace deprecated function
+                fragment.setTargetFragment(this, 0)
+                fragment.show(parentFragmentManager, "SettingsFragment.DIALOG:${preference.key}")
+            }
+            if (preference is CustomDialogPreference) {
+                showDialog(preference.createDialog())
+            } else {
+                super.onDisplayPreferenceDialog(preference)
+            }
+        }
+
+        companion object {
+            /**
+             * Password is considered strong when it is at least 8 chars long and contains 3 from those
+             * 4 categories:
+             * * lowercase
+             * * uppercase
+             * * numerics
+             * * other
+             * @param password
+             */
+            @VisibleForTesting
+            fun isWeakPassword(password: String?): Boolean {
+                if (password == null || password.length < 8) {
+                    return true
+                }
+                val groups = BitSet()
+                password.forEach { c ->
+                    groups.set(
+                        when {
+                            Character.isLetter(c) && Character.isLowerCase(c) -> 0
+                            Character.isLetter(c) && Character.isUpperCase(c) -> 1
+                            Character.isDigit(c) -> 2
+                            else -> 3
+                        }
+                    )
+                }
+                return groups.cardinality() < 3
+            }
+        }
+    }
+
+    class ConfirmationDialogFragment : DialogFragment() {
+        interface Callback {
+            fun onConfirmed(tag: String?)
+        }
+
+        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+            val args = requireArguments()
+            return AlertDialog.Builder(requireContext())
+                .setMessage(args.getInt("message"))
+                .setPositiveButton(args.getInt("buttontext")) { _, _ ->
+                    val callback = parentFragment as Callback? ?: throw IllegalArgumentException()
+                    callback.onConfirmed(args.getString("tag"))
+                }
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
+        }
+
+        companion object {
+            fun show(fm: FragmentManager, messageResId: Int, actionButtonTextResId: Int, tag: String) {
+                val f = ConfirmationDialogFragment()
+                f.arguments = bundleOf(
+                    "message" to messageResId,
+                    "buttontext" to actionButtonTextResId,
+                    "tag" to tag
+                )
+                f.show(fm, tag)
+            }
+        }
+    }
+
+    class ConfirmLeaveDialogFragment : DialogFragment() {
+        interface Callback {
+            fun onLeaveAndSave()
+            fun onLeaveAndDiscard()
+        }
+
+        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+            return AlertDialog.Builder(requireContext())
+                .setTitle(R.string.settings_server_confirm_leave_title)
+                .setMessage(R.string.settings_server_confirm_leave_message)
+                .setPositiveButton(R.string.save) { _, _ -> handleDone(true) }
+                .setNegativeButton(R.string.discard) { _, _ -> handleDone(false) }
+                .setNeutralButton(android.R.string.cancel, null)
+                .create()
+        }
+
+        private fun handleDone(confirmed: Boolean) {
+            val callback = parentFragment as Callback? ?: throw IllegalArgumentException()
+            if (confirmed) {
+                callback.onLeaveAndSave()
+            } else {
+                callback.onLeaveAndDiscard()
+            }
+        }
+    }
+
+    companion object {
+        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"
+        const val START_EXTRA_SERVER_PROPERTIES = "server_properties"
+        const val ITEM_UPDATE_WIDGET_ITEM = "item"
+        const val ITEM_UPDATE_WIDGET_COMMAND = "state"
+        const val ITEM_UPDATE_WIDGET_LABEL = "label"
+        const val ITEM_UPDATE_WIDGET_WIDGET_LABEL = "widgetLabel"
+        const val ITEM_UPDATE_WIDGET_MAPPED_STATE = "mappedState"
+        const val ITEM_UPDATE_WIDGET_ICON = "icon"
+        const val ITEM_UPDATE_WIDGET_THEME = "theme"
+        const val ITEM_UPDATE_WIDGET_SHOW_STATE = "show_state"
+        private const val STATE_KEY_RESULT = "result"
+
+        internal const val SNACKBAR_TAG_CLIENT_SSL_NOT_SUPPORTED = "clientSslNotSupported"
+        internal const val SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_PHONE = "bgTasksPermissionDeclinedPhone"
+        internal const val SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_WIFI = "bgTasksPermissionDeclinedWifi"
+        internal const val SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_BLUETOOTH = "bgTasksPermissionDeclinedBluetooth"
+        internal const val SNACKBAR_TAG_BG_TASKS_MISSING_PERMISSION_LOCATION = "bgTasksMissingPermissionLocation"
+        internal const val SNACKBAR_TAG_MISSING_PREFS = "missingPrefs"
+    }
+}
+
+interface CustomDialogPreference {
+    fun createDialog(): DialogFragment
+}
+
+data class PushNotificationStatus(
+    val message: String,
+    @DrawableRes val icon: Int,
+    val notifyUser: Boolean
+)
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/ConnectionSettingsFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/ConnectionSettingsFragment.kt
new file mode 100644
index 0000000000..6c87d85edd
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/ConnectionSettingsFragment.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2010-2022 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.content.Context
+import android.os.Bundle
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import androidx.core.os.bundleOf
+import androidx.preference.EditTextPreference
+import androidx.preference.Preference
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import org.openhab.habdroid.R
+import org.openhab.habdroid.model.ServerPath
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+
+class ConnectionSettingsFragment : PreferencesActivity.AbstractSettingsFragment() {
+    override val titleResId: Int @StringRes get() = requireArguments().getInt("title")
+
+    private lateinit var urlPreference: EditTextPreference
+    private lateinit var userNamePreference: EditTextPreference
+    private lateinit var passwordPreference: EditTextPreference
+    private lateinit var parent: ServerEditorFragment
+    private lateinit var path: ServerPath
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        parent = parentFragmentManager.getFragment(requireArguments(), "parent") as ServerEditorFragment
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(requireArguments().getInt("prefs"))
+
+        path = requireArguments().getParcelable("path", ServerPath::class.java)
+            ?: ServerPath("", null, null)
+
+        urlPreference = initEditor("url", path.url, R.drawable.ic_earth_grey_24dp) { value ->
+            val actualValue = if (!value.isNullOrEmpty()) value else getString(R.string.info_not_set)
+            getString(requireArguments().getInt("urlsummary"), actualValue)
+        }
+
+        userNamePreference = initEditor(
+            "username",
+            path.userName,
+            R.drawable.ic_person_outline_grey_24dp
+        ) { value ->
+            if (!value.isNullOrEmpty()) value else getString(R.string.info_not_set)
+        }
+        passwordPreference = initEditor(
+            "password",
+            path.password,
+            R.drawable.ic_shield_key_outline_grey_24dp
+        ) { value ->
+            getString(
+                when {
+                    value.isNullOrEmpty() -> R.string.info_not_set
+                    isWeakPassword(value) -> R.string.settings_openhab_password_summary_weak
+                    else -> R.string.settings_openhab_password_summary_strong
+                }
+            )
+        }
+
+        updateIconColors(urlPreference.text, userNamePreference.text, passwordPreference.text)
+    }
+
+    private fun initEditor(
+        key: String,
+        initialValue: String?,
+        @DrawableRes iconResId: Int,
+        summaryGenerator: (value: String?) -> CharSequence
+    ): EditTextPreference {
+        val preference = preferenceScreen.findPreference<EditTextPreference>(key)!!
+        preference.icon = DrawableCompat.wrap(ContextCompat.getDrawable(preference.context, iconResId)!!)
+        preference.text = initialValue
+        preference.setOnPreferenceChangeListener { pref, newValue ->
+            val url = if (pref === urlPreference) newValue as String else urlPreference.text
+            val userName = if (pref === userNamePreference) newValue as String else userNamePreference.text
+            val password = if (pref === passwordPreference) newValue as String else passwordPreference.text
+
+            updateIconColors(url, userName, password)
+            pref.summary = summaryGenerator(newValue as String)
+
+            if (url != null) {
+                val path = ServerPath(url, userName, password)
+                parent.onPathChanged(requireArguments().getString("key", ""), path)
+            }
+            true
+        }
+        preference.summary = summaryGenerator(initialValue)
+        return preference
+    }
+
+    private fun updateIconColors(url: String?, userName: String?, password: String?) {
+        updateIconColor(urlPreference) {
+            when {
+                url?.toHttpUrlOrNull()?.isHttps == true -> R.color.pref_icon_green
+                !url.isNullOrEmpty() -> R.color.pref_icon_red
+                else -> null
+            }
+        }
+        updateIconColor(userNamePreference) {
+            when {
+                url.isNullOrEmpty() -> null
+                userName.isNullOrEmpty() -> R.color.pref_icon_red
+                else -> R.color.pref_icon_green
+            }
+        }
+        updateIconColor(passwordPreference) {
+            when {
+                url.isNullOrEmpty() -> null
+                password.isNullOrEmpty() -> R.color.pref_icon_red
+                isWeakPassword(password) -> R.color.pref_icon_orange
+                else -> R.color.pref_icon_green
+            }
+        }
+    }
+
+    private fun updateIconColor(pref: Preference, colorGenerator: () -> Int?) {
+        pref.icon?.let { icon ->
+            val colorResId = colorGenerator()
+            if (colorResId != null) {
+                DrawableCompat.setTint(icon, ContextCompat.getColor(pref.context, colorResId))
+            } else {
+                DrawableCompat.setTintList(icon, null)
+            }
+        }
+    }
+
+    companion object {
+        fun newInstance(
+            key: String,
+            serverPath: ServerPath?,
+            prefsResId: Int,
+            titleResId: Int,
+            urlSummaryResId: Int,
+            parent: ServerEditorFragment
+        ): ConnectionSettingsFragment {
+            val f = ConnectionSettingsFragment()
+            val args = bundleOf(
+                "key" to key,
+                "path" to serverPath,
+                "prefs" to prefsResId,
+                "title" to titleResId,
+                "urlsummary" to urlSummaryResId
+            )
+            parent.parentFragmentManager.putFragment(args, "parent", parent)
+            f.arguments = args
+            return f
+        }
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DrawerEntriesMenuFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DrawerEntriesMenuFragment.kt
new file mode 100644
index 0000000000..6839d6e6b3
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/DrawerEntriesMenuFragment.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010-2022 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.nfc.NfcAdapter
+import android.os.Bundle
+import androidx.annotation.StringRes
+import org.openhab.habdroid.R
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+import org.openhab.habdroid.util.PrefKeys
+import org.openhab.habdroid.util.Util
+import org.openhab.habdroid.util.getPreference
+
+class DrawerEntriesMenuFragment : PreferencesActivity.AbstractSettingsFragment() {
+    override val titleResId: Int @StringRes get() = R.string.drawer_entries
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(R.xml.preferences_drawer_entries)
+
+        val showSitemapInDrawerPref = getPreference(PrefKeys.SHOW_SITEMAPS_IN_DRAWER)
+
+        showSitemapInDrawerPref.setOnPreferenceChangeListener { _, _ ->
+            parentActivity.addResultFlag(PreferencesActivity.RESULT_EXTRA_SITEMAP_DRAWER_CHANGED)
+            true
+        }
+
+        if (NfcAdapter.getDefaultAdapter(requireContext()) == null && !Util.isEmulator()) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.DRAWER_ENTRY_NFC)
+        }
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/MainSettingsFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/MainSettingsFragment.kt
new file mode 100644
index 0000000000..f40922528b
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/MainSettingsFragment.kt
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2010-2022 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.app.KeyguardManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.ColorInt
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.content.ContextCompat
+import androidx.core.content.edit
+import androidx.fragment.app.commit
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.SwitchPreference
+import com.jaredrummler.android.colorpicker.ColorPreferenceCompat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.openhab.habdroid.R
+import org.openhab.habdroid.background.tiles.AbstractTileService
+import org.openhab.habdroid.background.tiles.getTileData
+import org.openhab.habdroid.core.CloudMessagingHelper
+import org.openhab.habdroid.core.connection.CloudConnection
+import org.openhab.habdroid.core.connection.ConnectionFactory
+import org.openhab.habdroid.model.ServerConfiguration
+import org.openhab.habdroid.model.ServerProperties
+import org.openhab.habdroid.ui.AbstractBaseActivity
+import org.openhab.habdroid.ui.LogActivity
+import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+import org.openhab.habdroid.ui.preference.widgets.NotificationPollingPreference
+import org.openhab.habdroid.util.CacheManager
+import org.openhab.habdroid.util.CrashReportingHelper
+import org.openhab.habdroid.util.PrefKeys
+import org.openhab.habdroid.util.getConfiguredServerIds
+import org.openhab.habdroid.util.getDayNightMode
+import org.openhab.habdroid.util.getNextAvailableServerId
+import org.openhab.habdroid.util.getNotificationTone
+import org.openhab.habdroid.util.getPreference
+import org.openhab.habdroid.util.getPrefs
+import org.openhab.habdroid.util.getPrimaryServerId
+import org.openhab.habdroid.util.getStringOrFallbackIfEmpty
+import org.openhab.habdroid.util.getStringOrNull
+import org.openhab.habdroid.util.isInstalled
+import org.openhab.habdroid.util.isTaskerPluginEnabled
+
+class MainSettingsFragment : PreferencesActivity.AbstractSettingsFragment(), ConnectionFactory.UpdateListener {
+    override val titleResId: Int @StringRes get() = R.string.action_settings
+    @ColorInt
+    var previousColor: Int = 0
+
+    private var notificationPollingPref: NotificationPollingPreference? = null
+    private var notificationStatusHint: Preference? = null
+    private var selectRingToneCallback = registerForActivityResult(
+        ActivityResultContracts.StartActivityForResult()
+    ) { result ->
+        Log.d(TAG, "selectRingToneCallback: $result")
+        val data = result.data ?: return@registerForActivityResult
+        val ringtoneUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)
+        val ringtonePref = getPreference(PrefKeys.NOTIFICATION_TONE)
+        updateRingtonePreferenceSummary(ringtonePref, ringtoneUri)
+        prefs.edit {
+            putString(PrefKeys.NOTIFICATION_TONE, ringtoneUri?.toString() ?: "")
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        updateScreenLockStateAndSummary(
+            prefs.getStringOrFallbackIfEmpty(PrefKeys.SCREEN_LOCK, getString(R.string.settings_screen_lock_off_value))
+        )
+        populateServerPrefs()
+        ConnectionFactory.addListener(this)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            updateTileSummary()
+        }
+    }
+
+    override fun onStop() {
+        super.onStop()
+        ConnectionFactory.removeListener(this)
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(R.xml.preferences)
+
+        val addServerPref = getPreference("add_server")
+        val sendDeviceInfoPref = getPreference(PrefKeys.SUBSCREEN_SEND_DEVICE_INFO)
+        notificationPollingPref =
+            getPreference(PrefKeys.FOSS_NOTIFICATIONS_ENABLED) as NotificationPollingPreference
+        notificationStatusHint = getPreference(PrefKeys.NOTIFICATION_STATUS_HINT)
+        val drawerEntriesPrefs = getPreference(PrefKeys.DRAWER_ENTRIES)
+        val themePref = getPreference(PrefKeys.THEME)
+        val accentColorPref = getPreference(PrefKeys.ACCENT_COLOR) as ColorPreferenceCompat
+        val clearCachePref = getPreference(PrefKeys.CLEAR_CACHE)
+        val fullscreenPref = getPreference(PrefKeys.FULLSCREEN)
+        val launcherPref = getPreference(PrefKeys.LAUNCHER)
+        val iconFormatPref = getPreference(PrefKeys.ICON_FORMAT)
+        val ringtonePref = getPreference(PrefKeys.NOTIFICATION_TONE)
+        val vibrationPref = getPreference(PrefKeys.NOTIFICATION_VIBRATION)
+        val ringtoneVibrationPref = getPreference(PrefKeys.NOTIFICATION_TONE_VIBRATION)
+        val viewLogPref = getPreference(PrefKeys.LOG)
+        val screenLockPref = getPreference(PrefKeys.SCREEN_LOCK)
+        val tilePref = getPreference(PrefKeys.SUBSCREEN_TILE)
+        val crashReporting = getPreference(PrefKeys.CRASH_REPORTING)
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            val dataSaverPref = getPreference(PrefKeys.DATA_SAVER) as SwitchPreference
+            dataSaverPref.setSwitchTextOff(R.string.data_saver_off_pre_n)
+        }
+
+        updateRingtonePreferenceSummary(ringtonePref, prefs.getNotificationTone())
+        updateVibrationPreferenceIcon(vibrationPref, prefs.getStringOrNull(PrefKeys.NOTIFICATION_VIBRATION))
+
+        addServerPref.setOnPreferenceClickListener {
+            val nextServerId = prefs.getNextAvailableServerId()
+            val nextName = if (prefs.getConfiguredServerIds().isEmpty()) {
+                getString(R.string.openhab)
+            } else {
+                getString(R.string.settings_server_default_name, nextServerId)
+            }
+            val f = ServerEditorFragment.newInstance(
+                ServerConfiguration(nextServerId, nextName, null, null, null, null, null, false)
+            )
+            parentActivity.openSubScreen(f)
+            true
+        }
+
+        sendDeviceInfoPref.setOnPreferenceClickListener {
+            parentActivity.openSubScreen(SendDeviceInfoSettingsFragment())
+            false
+        }
+
+        if (CloudMessagingHelper.isPollingBuild()) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_STATUS_HINT)
+        } else {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.FOSS_NOTIFICATIONS_ENABLED)
+        }
+        updateNotificationStatusSummaries()
+        notificationPollingPref?.setOnPreferenceChangeListener { _, _ ->
+            parentActivity.launch(Dispatchers.Main) {
+                updateNotificationStatusSummaries()
+            }
+            true
+        }
+
+        drawerEntriesPrefs.setOnPreferenceClickListener {
+            parentActivity.openSubScreen(DrawerEntriesMenuFragment())
+            false
+        }
+
+        themePref.setOnPreferenceChangeListener { _, _ ->
+            // getDayNightMode() needs the new preference value, so delay the call until
+            // after this listener has returned
+            parentActivity.launch(Dispatchers.Main) {
+                val mode = parentActivity.getPrefs().getDayNightMode(parentActivity)
+                AppCompatDelegate.setDefaultNightMode(mode)
+                parentActivity.handleThemeChange()
+            }
+            true
+        }
+
+        previousColor = prefs.getInt(accentColorPref.key, 0)
+        accentColorPref.setOnPreferenceChangeListener { _, newValue ->
+            parentFragmentManager.findFragmentByTag(accentColorPref.fragmentTag)?.let { dialog ->
+                parentFragmentManager.commit(allowStateLoss = true) {
+                    remove(dialog)
+                }
+            }
+            if (previousColor != newValue) {
+                parentActivity.handleThemeChange()
+            }
+            true
+        }
+
+        clearCachePref.setOnPreferenceClickListener { pref ->
+            clearImageCache(pref.context)
+            true
+        }
+
+        if (!prefs.isTaskerPluginEnabled() && !isAutomationAppInstalled()) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.TASKER_PLUGIN_ENABLED)
+        }
+
+        if (CrashReportingHelper.canBeDisabledByUser()) {
+            crashReporting.setOnPreferenceClickListener {
+                CrashReportingHelper.initialize(requireActivity().application)
+                true
+            }
+        } else {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.CRASH_REPORTING)
+        }
+
+        viewLogPref.setOnPreferenceClickListener { preference ->
+            val logIntent = Intent(preference.context, LogActivity::class.java)
+            startActivity(logIntent)
+            true
+        }
+
+        fullscreenPref.setOnPreferenceChangeListener { _, newValue ->
+            (activity as AbstractBaseActivity).setFullscreen(newValue as Boolean)
+            true
+        }
+
+        launcherPref.setOnPreferenceChangeListener { pref, newValue ->
+            val context = pref.context
+            val launcherAlias = ComponentName(context, "${context.packageName}.ui.LauncherActivityAlias")
+            val newState = if (newValue as Boolean)
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+            else
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+            context.packageManager.setComponentEnabledSetting(launcherAlias, newState, PackageManager.DONT_KILL_APP)
+            true
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            Log.d(TAG, "Removing notification prefs for < 25")
+            preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_TONE)
+            preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_VIBRATION)
+
+            ringtoneVibrationPref.setOnPreferenceClickListener { pref ->
+                val i = Intent(Settings.ACTION_SETTINGS).apply {
+                    action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
+                    putExtra(Settings.EXTRA_APP_PACKAGE, pref.context.packageName)
+                }
+                startActivity(i)
+                true
+            }
+        } else {
+            Log.d(TAG, "Removing notification prefs for >= 25")
+            preferenceScreen.removePreferenceRecursively(PrefKeys.NOTIFICATION_TONE_VIBRATION)
+
+            ringtonePref.setOnPreferenceClickListener { pref ->
+                val currentTone = prefs.getNotificationTone()
+                val chooserIntent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
+                    putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
+                    putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI)
+                    putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, pref.title)
+                    putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
+                    putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
+                    putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentTone)
+                }
+                selectRingToneCallback.launch(chooserIntent)
+                true
+            }
+
+            vibrationPref.setOnPreferenceChangeListener { pref, newValue ->
+                updateVibrationPreferenceIcon(pref, newValue as String?)
+                true
+            }
+        }
+
+        screenLockPref.setOnPreferenceChangeListener { _, newValue ->
+            updateScreenLockStateAndSummary(newValue as String)
+            true
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            tilePref.setOnPreferenceClickListener {
+                parentActivity.openSubScreen(TileOverviewFragment())
+                false
+            }
+            updateTileSummary()
+        } else {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.SUBSCREEN_TILE)
+        }
+
+        val flags = activity
+            ?.intent
+            ?.getParcelableExtra(PreferencesActivity.START_EXTRA_SERVER_PROPERTIES, ServerProperties::class.java)
+            ?.flags
+            ?: prefs.getInt(PrefKeys.PREV_SERVER_FLAGS, 0)
+
+        if (flags and ServerProperties.SERVER_FLAG_ICON_FORMAT_SUPPORT == 0 ||
+            flags and ServerProperties.SERVER_FLAG_SUPPORTS_ANY_FORMAT_ICON != 0
+        ) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.ICON_FORMAT)
+        } else {
+            iconFormatPref.setOnPreferenceChangeListener { pref, _ ->
+                val context = pref.context
+                clearImageCache(context)
+                ItemUpdateWidget.updateAllWidgets(context)
+                true
+            }
+        }
+        if (flags and ServerProperties.SERVER_FLAG_CHART_SCALING_SUPPORT == 0) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.CHART_SCALING)
+        }
+    }
+
+    private fun populateServerPrefs() {
+        val context = preferenceManager.context
+        val connCategory = getPreference("connection") as PreferenceCategory
+        (0 until connCategory.preferenceCount)
+            .map { index -> connCategory.getPreference(index) }
+            .filter { pref -> pref.key?.startsWith("server_") == true }
+            .forEach { pref -> connCategory.removePreference(pref) }
+
+        prefs.getConfiguredServerIds().forEach { serverId ->
+            val config = ServerConfiguration.load(prefs, secretPrefs, serverId)
+            if (config != null) {
+                val pref = Preference(context)
+                pref.title = context.getString(R.string.server_with_name, config.name)
+                pref.key = "server_$serverId"
+                pref.order = 10 * serverId
+                pref.setOnPreferenceClickListener {
+                    parentActivity.openSubScreen(ServerEditorFragment.newInstance(config))
+                    true
+                }
+                pref.icon = if (serverId == prefs.getPrimaryServerId() && prefs.getConfiguredServerIds().size > 1) {
+                    ContextCompat.getDrawable(context, R.drawable.ic_star_border_grey_24dp)
+                } else {
+                    null
+                }
+                connCategory.addPreference(pref)
+                // The pref needs to be attached for doing this
+                pref.dependency = PrefKeys.DEMO_MODE
+            }
+        }
+    }
+
+    private fun clearImageCache(context: Context) {
+        // Get launch intent for application
+        val restartIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
+        restartIntent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        // Finish current activity
+        activity?.finish()
+        CacheManager.getInstance(context).clearCache(true)
+        // Start launch activity
+        startActivity(restartIntent)
+    }
+
+    private fun updateNotificationStatusSummaries() {
+        parentActivity.launch {
+            notificationPollingPref?.updateSummaryAndIcon()
+            notificationStatusHint?.apply {
+                val data = CloudMessagingHelper.getPushNotificationStatus(this.context)
+                summary = data.message
+                setIcon(data.icon)
+            }
+        }
+    }
+
+    private fun updateScreenLockStateAndSummary(value: String?) {
+        val pref = findPreference<Preference>(PrefKeys.SCREEN_LOCK) ?: return
+        val km = ContextCompat.getSystemService(pref.context, KeyguardManager::class.java)!!
+        val locked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) km.isDeviceSecure else km.isKeyguardSecure
+        pref.isEnabled = locked
+        pref.summary = getString(
+            when {
+                !locked -> R.string.settings_screen_lock_nolock_summary
+                value == getString(R.string.settings_screen_lock_on_value) -> R.string.settings_screen_lock_on_summary
+                value == getString(R.string.settings_screen_lock_kiosk_value) ->
+                    R.string.settings_screen_lock_kiosk_summary
+                else -> R.string.settings_screen_lock_off_summary
+            }
+        )
+    }
+
+    private fun updateRingtonePreferenceSummary(pref: Preference, newValue: Uri?) {
+        if (newValue == null) {
+            pref.setIcon(R.drawable.ic_bell_off_outline_grey_24dp)
+            pref.setSummary(R.string.settings_ringtone_none)
+        } else {
+            pref.setIcon(R.drawable.ic_bell_ring_outline_grey_24dp)
+            val ringtone = RingtoneManager.getRingtone(activity, newValue)
+            pref.summary = try {
+                ringtone?.getTitle(activity)
+            } catch (e: SecurityException) {
+                getString(R.string.settings_ringtone_on_external)
+            }
+        }
+    }
+
+    private fun updateVibrationPreferenceIcon(pref: Preference, newValue: String?) {
+        val noVibration = newValue == getString(R.string.settings_notification_vibration_value_off)
+        pref.setIcon(if (noVibration) R.drawable.ic_vibrate_off_grey_24dp else R.drawable.ic_vibration_grey_24dp)
+    }
+
+    @RequiresApi(Build.VERSION_CODES.N)
+    private fun updateTileSummary() {
+        val activeTileCount = (1..AbstractTileService.TILE_COUNT)
+            .mapNotNull { id -> prefs.getTileData(id) }
+            .size
+        val pref = getPreference(PrefKeys.SUBSCREEN_TILE)
+        pref.summary = resources.getQuantityString(R.plurals.tile_active_number, activeTileCount, activeTileCount)
+    }
+
+    private fun isAutomationAppInstalled(): Boolean {
+        val pm = activity?.packageManager ?: return false
+        // These package names must be added to the manifest as well
+        return listOf("net.dinglisch.android.taskerm", "com.twofortyfouram.locale").any { pkg ->
+            pm.isInstalled(pkg)
+        }
+    }
+
+    override fun onActiveConnectionChanged() {
+        // no-op
+    }
+
+    override fun onPrimaryConnectionChanged() {
+        updateNotificationStatusSummaries()
+    }
+
+    override fun onActiveCloudConnectionChanged(connection: CloudConnection?) {
+        // no-op
+    }
+
+    override fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) {
+        updateNotificationStatusSummaries()
+    }
+
+    companion object {
+        private val TAG = MainSettingsFragment::class.java.simpleName
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/SendDeviceInfoSettingsFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/SendDeviceInfoSettingsFragment.kt
new file mode 100644
index 0000000000..0827534aac
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/SendDeviceInfoSettingsFragment.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2010-2022 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.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.StringRes
+import org.openhab.habdroid.R
+import org.openhab.habdroid.background.BackgroundTasksManager
+import org.openhab.habdroid.background.EventListenerService
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+import org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
+import org.openhab.habdroid.util.PrefKeys
+import org.openhab.habdroid.util.getPreference
+import org.openhab.habdroid.util.getPrefixForBgTasks
+import org.openhab.habdroid.util.isInstalled
+
+class SendDeviceInfoSettingsFragment :
+    PreferencesActivity.AbstractSettingsFragment(),
+    SharedPreferences.OnSharedPreferenceChangeListener {
+    override val titleResId: Int @StringRes get() = R.string.send_device_info_to_server_short
+    private lateinit var phoneStatePref: ItemUpdatingPreference
+    private lateinit var wifiSsidPref: ItemUpdatingPreference
+    private lateinit var bluetoothPref: ItemUpdatingPreference
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(R.xml.preferences_device_information)
+
+        val prefixHint = getPreference(PrefKeys.DEV_ID_PREFIX_BG_TASKS)
+        val foregroundServicePref = getPreference(PrefKeys.SEND_DEVICE_INFO_FOREGROUND_SERVICE)
+        phoneStatePref = getPreference(PrefKeys.SEND_PHONE_STATE) as ItemUpdatingPreference
+        wifiSsidPref = getPreference(PrefKeys.SEND_WIFI_SSID) as ItemUpdatingPreference
+        bluetoothPref = getPreference(PrefKeys.SEND_BLUETOOTH_DEVICES) as ItemUpdatingPreference
+
+        phoneStatePref.setOnPreferenceChangeListener { _, newValue ->
+            requestPermissionIfEnabled(
+                newValue,
+                BackgroundTasksManager.getRequiredPermissionsForTask(PrefKeys.SEND_PHONE_STATE),
+                PERMISSIONS_REQUEST_FOR_CALL_STATE
+            )
+            true
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            wifiSsidPref.setSummaryOn(getString(R.string.settings_wifi_ssid_summary_on_location_on))
+        }
+        wifiSsidPref.setOnPreferenceChangeListener { _, newValue ->
+            requestPermissionIfEnabled(
+                newValue,
+                BackgroundTasksManager.getRequiredPermissionsForTask(PrefKeys.SEND_WIFI_SSID),
+                PERMISSIONS_REQUEST_FOR_WIFI_NAME
+            )
+            true
+        }
+
+        bluetoothPref.setOnPreferenceChangeListener { _, newValue ->
+            requestPermissionIfEnabled(
+                newValue,
+                BackgroundTasksManager.getRequiredPermissionsForTask(PrefKeys.SEND_BLUETOOTH_DEVICES),
+                PERMISSIONS_REQUEST_FOR_BLUETOOTH_DEVICES
+            )
+            true
+        }
+
+        if (activity?.packageManager?.isInstalled("nodomain.freeyourgadget.gadgetbridge") == false) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.SEND_GADGETBRIDGE)
+        }
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.SEND_DND_MODE)
+            preferenceScreen.removePreferenceRecursively(PrefKeys.SEND_DEVICE_INFO_FOREGROUND_SERVICE)
+        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            foregroundServicePref.setSummary(R.string.send_device_info_foreground_service_summary_pre_o)
+        }
+
+        foregroundServicePref.setOnPreferenceChangeListener { preference, newValue ->
+            EventListenerService.startOrStopService(preference.context, newValue as Boolean)
+            true
+        }
+
+        BackgroundTasksManager.KNOWN_KEYS.forEach { key ->
+            findPreference<ItemUpdatingPreference>(key)?.startObserving(this)
+        }
+
+        prefs.registerOnSharedPreferenceChangeListener(this)
+
+        val prefix = prefs.getPrefixForBgTasks()
+        prefixHint.summary = if (prefix.isEmpty()) {
+            prefixHint.context.getString(R.string.send_device_info_item_prefix_summary_not_set)
+        } else {
+            prefixHint.context.getString(R.string.send_device_info_item_prefix_summary, prefix)
+        }
+    }
+
+    override fun onDetach() {
+        prefs.unregisterOnSharedPreferenceChangeListener(this)
+        super.onDetach()
+    }
+
+    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+        if (key in BackgroundTasksManager.KNOWN_PERIODIC_KEYS) {
+            EventListenerService.startOrStopService(requireContext())
+        }
+    }
+
+    private fun requestPermissionIfEnabled(
+        newValue: Any?,
+        permissions: Array<String>?,
+        requestCode: Int
+    ) {
+        @Suppress("UNCHECKED_CAST")
+        if ((newValue as Pair<Boolean, String>).first) {
+            parentActivity.requestPermissionsIfRequired(permissions, requestCode)
+        }
+    }
+
+    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
+        val context = phoneStatePref.context
+
+        when (requestCode) {
+            PERMISSIONS_REQUEST_FOR_CALL_STATE -> {
+                if (grantResults.firstOrNull { it != PackageManager.PERMISSION_GRANTED } != null) {
+                    parentActivity.showSnackbar(
+                        PreferencesActivity.SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_PHONE,
+                        R.string.settings_phone_state_permission_denied
+                    )
+                    phoneStatePref.setValue(checked = false)
+                } else {
+                    BackgroundTasksManager.scheduleWorker(context, PrefKeys.SEND_PHONE_STATE, true)
+                }
+            }
+            PERMISSIONS_REQUEST_FOR_WIFI_NAME -> {
+                if (grantResults.firstOrNull { it != PackageManager.PERMISSION_GRANTED } != null) {
+                    parentActivity.showSnackbar(
+                        PreferencesActivity.SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_WIFI,
+                        R.string.settings_wifi_ssid_permission_denied
+                    )
+                    wifiSsidPref.setValue(checked = false)
+                } else {
+                    BackgroundTasksManager.scheduleWorker(context, PrefKeys.SEND_WIFI_SSID, true)
+                }
+            }
+            PERMISSIONS_REQUEST_FOR_BLUETOOTH_DEVICES -> {
+                if (grantResults.firstOrNull { it != PackageManager.PERMISSION_GRANTED } != null) {
+                    parentActivity.showSnackbar(
+                        PreferencesActivity.SNACKBAR_TAG_BG_TASKS_PERMISSION_DECLINED_BLUETOOTH,
+                        R.string.settings_bluetooth_devices_permission_denied
+                    )
+                    bluetoothPref.setValue(checked = false)
+                } else {
+                    BackgroundTasksManager.scheduleWorker(context, PrefKeys.SEND_BLUETOOTH_DEVICES, true)
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val PERMISSIONS_REQUEST_FOR_CALL_STATE = 0
+        private const val PERMISSIONS_REQUEST_FOR_WIFI_NAME = 1
+        private const val PERMISSIONS_REQUEST_FOR_BLUETOOTH_DEVICES = 2
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/ServerEditorFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/ServerEditorFragment.kt
new file mode 100644
index 0000000000..ec0383ff52
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/ServerEditorFragment.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2010-2022 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 android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.edit
+import androidx.core.os.bundleOf
+import androidx.preference.EditTextPreference
+import androidx.preference.Preference
+import androidx.work.WorkManager
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import org.openhab.habdroid.R
+import org.openhab.habdroid.background.BackgroundTasksManager
+import org.openhab.habdroid.model.ServerConfiguration
+import org.openhab.habdroid.model.ServerPath
+import org.openhab.habdroid.model.toWifiSsids
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+import org.openhab.habdroid.ui.preference.widgets.SslClientCertificatePreference
+import org.openhab.habdroid.ui.preference.widgets.WifiSsidInputPreference
+import org.openhab.habdroid.util.HttpClient
+import org.openhab.habdroid.util.PrefKeys
+import org.openhab.habdroid.util.getConfiguredServerIds
+import org.openhab.habdroid.util.getPreference
+import org.openhab.habdroid.util.getPrimaryServerId
+import org.openhab.habdroid.util.putPrimaryServerId
+import org.openhab.habdroid.util.updateDefaultSitemap
+
+class ServerEditorFragment :
+    PreferencesActivity.AbstractSettingsFragment(),
+    PreferencesActivity.ConfirmationDialogFragment.Callback,
+    PreferencesActivity.ConfirmLeaveDialogFragment.Callback {
+    private lateinit var config: ServerConfiguration
+    private lateinit var initialConfig: ServerConfiguration
+    private var markAsPrimary = false
+
+    override val titleResId: Int get() = R.string.settings_edit_server
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        config = requireArguments().getParcelable("config", ServerConfiguration::class.java)!!
+        initialConfig = config
+        super.onCreate(savedInstanceState)
+        setHasOptionsMenu(true)
+
+        val backCallback = object : OnBackPressedCallback(true) {
+            override fun handleOnBackPressed() {
+                if (initialConfig != config) {
+                    PreferencesActivity.ConfirmLeaveDialogFragment().show(childFragmentManager, "dialog_confirm_leave")
+                } else {
+                    isEnabled = false
+                    parentActivity.onBackPressedDispatcher.onBackPressed()
+                }
+            }
+        }
+
+        parentActivity.onBackPressedDispatcher.addCallback(this, backCallback)
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        super.onCreateOptionsMenu(menu, inflater)
+        inflater.inflate(R.menu.server_editor, menu)
+        val deleteItem = menu.findItem(R.id.delete)
+        deleteItem.isVisible = prefs.getConfiguredServerIds().contains(config.id)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        return when (item.itemId) {
+            R.id.save -> {
+                saveAndQuit()
+                true
+            }
+            R.id.delete -> {
+                PreferencesActivity.ConfirmationDialogFragment.show(
+                    childFragmentManager,
+                    R.string.settings_server_confirm_deletion,
+                    R.string.delete,
+                    "delete_server_confirmation"
+                )
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+
+    private fun saveAndQuit() {
+        if (config.name.isEmpty() || (config.localPath == null && config.remotePath == null)) {
+            parentActivity.showSnackbar(
+                PreferencesActivity.SNACKBAR_TAG_MISSING_PREFS,
+                R.string.settings_server_at_least_name_and_connection
+            )
+            return
+        }
+        config.saveToPrefs(prefs, secretPrefs)
+        if (markAsPrimary) {
+            prefs.edit {
+                putPrimaryServerId(config.id)
+            }
+        }
+        parentActivity.invalidateOptionsMenu()
+        parentFragmentManager.popBackStack() // close ourself
+    }
+
+    override fun onConfirmed(tag: String?) = when (tag) {
+        "delete_server_confirmation" -> {
+            config.removeFromPrefs(prefs, secretPrefs)
+            WorkManager.getInstance(preferenceManager.context).apply {
+                cancelAllWorkByTag(BackgroundTasksManager.buildWorkerTagForServer(config.id))
+                pruneWork()
+            }
+            parentFragmentManager.popBackStack() // close ourself
+        }
+        else -> {}
+    }
+
+    override fun onLeaveAndSave() {
+        saveAndQuit()
+    }
+
+    override fun onLeaveAndDiscard() {
+        parentActivity.invalidateOptionsMenu()
+        parentFragmentManager.popBackStack() // close ourself
+    }
+
+    override fun onStart() {
+        super.onStart()
+        updateConnectionSummary("local", config.localPath)
+        updateConnectionSummary("remote", config.remotePath)
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(R.xml.preferences_server)
+
+        val serverNamePref = getPreference("name") as EditTextPreference
+        serverNamePref.text = config.name
+        serverNamePref.setOnPreferenceChangeListener { _, newValue ->
+            config = ServerConfiguration(
+                config.id,
+                newValue as String,
+                config.localPath,
+                config.remotePath,
+                config.sslClientCert,
+                config.defaultSitemap,
+                config.wifiSsids,
+                config.restrictToWifiSsids
+            )
+            parentActivity.invalidateOptionsMenu()
+            true
+        }
+
+        val localConnPref = getPreference("local")
+        localConnPref.setOnPreferenceClickListener {
+            parentActivity.openSubScreen(
+                ConnectionSettingsFragment.newInstance(
+                    localConnPref.key,
+                    config.localPath,
+                    R.xml.preferences_local_connection,
+                    R.string.settings_openhab_connection,
+                    R.string.settings_openhab_url_summary,
+                    this
+                )
+            )
+            false
+        }
+
+        val remoteConnPref = getPreference("remote")
+        remoteConnPref.setOnPreferenceClickListener {
+            parentActivity.openSubScreen(
+                ConnectionSettingsFragment.newInstance(
+                    remoteConnPref.key,
+                    config.remotePath,
+                    R.xml.preferences_remote_connection,
+                    R.string.settings_openhab_alt_connection,
+                    R.string.settings_openhab_alturl_summary,
+                    this
+                )
+            )
+            false
+        }
+
+        val clientCertPref = getPreference("clientcert") as SslClientCertificatePreference
+        clientCertPref.setOnPreferenceChangeListener { _, newValue ->
+            config = ServerConfiguration(
+                config.id,
+                config.name,
+                config.localPath,
+                config.remotePath,
+                newValue as String?,
+                config.defaultSitemap,
+                config.wifiSsids,
+                config.restrictToWifiSsids
+            )
+            true
+        }
+        clientCertPref.setValue(config.sslClientCert)
+
+        val clearDefaultSitemapPref = getPreference(PrefKeys.CLEAR_DEFAULT_SITEMAP)
+        if (config.defaultSitemap?.name.isNullOrEmpty()) {
+            handleNoDefaultSitemap(clearDefaultSitemapPref)
+        } else {
+            clearDefaultSitemapPref.summary = getString(
+                R.string.settings_current_default_sitemap, config.defaultSitemap?.label.orEmpty()
+            )
+        }
+        clearDefaultSitemapPref.setOnPreferenceClickListener { preference ->
+            preference.sharedPreferences!!.updateDefaultSitemap(null, null, config.id)
+            handleNoDefaultSitemap(preference)
+            parentActivity.addResultFlag(PreferencesActivity.RESULT_EXTRA_SITEMAP_CLEARED)
+            true
+        }
+
+        val wifiSsidPref = getPreference("wifi_ssid") as WifiSsidInputPreference
+        if (prefs.getConfiguredServerIds().isEmpty()) {
+            preferenceScreen.removePreferenceRecursively(PrefKeys.PRIMARY_SERVER_PREF)
+            preferenceScreen.removePreferenceRecursively("wifi_ssid")
+        } else {
+            val primaryServerPref = getPreference(PrefKeys.PRIMARY_SERVER_PREF)
+            updatePrimaryServerPrefState(primaryServerPref, config.id == prefs.getPrimaryServerId())
+            primaryServerPref.setOnPreferenceClickListener {
+                if (prefs.getConfiguredServerIds().contains(config.id)) {
+                    prefs.edit {
+                        putPrimaryServerId(config.id)
+                    }
+                } else {
+                    markAsPrimary = true
+                }
+                updatePrimaryServerPrefState(primaryServerPref, true)
+                true
+            }
+
+            wifiSsidPref.setValue(
+                Pair(
+                    config.wifiSsids?.joinToString("\n").orEmpty(),
+                    config.restrictToWifiSsids
+                )
+            )
+            wifiSsidPref.setOnPreferenceChangeListener { _, newValue ->
+                @Suppress("UNCHECKED_CAST")
+                val value = newValue as Pair<String, Boolean>
+                val ssids = value.first.toWifiSsids()
+                // Don't restrict if no SSID is set
+                val restrictToSsids = if (ssids.isEmpty()) false else value.second
+                config = ServerConfiguration(
+                    config.id,
+                    config.name,
+                    config.localPath,
+                    config.remotePath,
+                    config.sslClientCert,
+                    config.defaultSitemap,
+                    ssids,
+                    restrictToSsids
+                )
+                true
+            }
+        }
+
+        val advancedPrefs = getPreference("advanced_prefs")
+        advancedPrefs.setOnPreferenceClickListener {
+            advancedPrefs.isVisible = false
+            clientCertPref.isVisible = true
+            wifiSsidPref.isVisible = true
+            false
+        }
+    }
+
+    private fun updatePrimaryServerPrefState(pref: Preference, isPrimary: Boolean) {
+        pref.summary = if (isPrimary) {
+            getString(R.string.settings_server_primary_summary_is_primary)
+        } else {
+            val nameOfPrimary = ServerConfiguration.load(prefs, secretPrefs, prefs.getPrimaryServerId())?.name
+            getString(R.string.settings_server_primary_summary_is_not_primary, nameOfPrimary)
+        }
+    }
+
+    private fun handleNoDefaultSitemap(pref: Preference) {
+        pref.isEnabled = false
+        pref.setSummary(R.string.settings_no_default_sitemap)
+    }
+
+    fun onPathChanged(key: String, path: ServerPath) {
+        config = if (key == "local") {
+            ServerConfiguration(
+                config.id,
+                config.name,
+                path,
+                config.remotePath,
+                config.sslClientCert,
+                config.defaultSitemap,
+                config.wifiSsids,
+                config.restrictToWifiSsids
+            )
+        } else {
+            ServerConfiguration(
+                config.id,
+                config.name,
+                config.localPath,
+                path,
+                config.sslClientCert,
+                config.defaultSitemap,
+                config.wifiSsids,
+                config.restrictToWifiSsids
+            )
+        }
+        parentActivity.invalidateOptionsMenu()
+    }
+
+    private fun updateConnectionSummary(key: String, path: ServerPath?) {
+        fun insecureMessage(beautyUrl: String, reason: Int) =
+            getString(R.string.settings_insecure_connection_summary, beautyUrl, getString(reason))
+
+        val pref = getPreference(key)
+        val beautyUrl = beautifyUrl(path?.url.orEmpty())
+        pref.summary = when {
+            path == null || path.url.isEmpty() ->
+                getString(R.string.info_not_set)
+            path.url.toHttpUrlOrNull()?.isHttps == false ->
+                insecureMessage(beautyUrl, R.string.settings_insecure_connection_no_https)
+            !path.hasAuthentication() && config.sslClientCert == null ->
+                insecureMessage(beautyUrl, R.string.settings_insecure_connection_no_auth)
+            isWeakPassword(path.password) ->
+                insecureMessage(beautyUrl, R.string.settings_openhab_password_summary_weak)
+            else ->
+                getString(R.string.settings_connection_summary, beautyUrl)
+        }
+    }
+
+    companion object {
+        fun newInstance(config: ServerConfiguration): ServerEditorFragment {
+            val f = ServerEditorFragment()
+            f.arguments = bundleOf("config" to config)
+            return f
+        }
+
+        @VisibleForTesting
+        fun beautifyUrl(url: String): String {
+            val host = url.toHttpUrlOrNull()?.host ?: url
+            return if (HttpClient.isMyOpenhab(host)) "myopenHAB" else host
+        }
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/TileOverviewFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/TileOverviewFragment.kt
new file mode 100644
index 0000000000..9d187882af
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/TileOverviewFragment.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010-2022 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.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+import androidx.preference.Preference
+import androidx.preference.forEachIndexed
+import org.openhab.habdroid.R
+import org.openhab.habdroid.background.tiles.AbstractTileService
+import org.openhab.habdroid.background.tiles.getTileData
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+
+@RequiresApi(Build.VERSION_CODES.N)
+class TileOverviewFragment : PreferencesActivity.AbstractSettingsFragment() {
+    override val titleResId: Int @StringRes get() = R.string.tiles_for_quick_settings
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(R.xml.preferences_tile_overview)
+        for (tileId in 1..AbstractTileService.TILE_COUNT) {
+            val tilePref = Preference(preferenceManager.context).apply {
+                key = "tile_$tileId"
+                title = getString(R.string.tile_number, tileId)
+                isPersistent = false
+            }
+            tilePref.setOnPreferenceClickListener {
+                parentActivity.openSubScreen(TileSettingsFragment.newInstance(tileId))
+                false
+            }
+            preferenceScreen.addPreference(tilePref)
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        preferenceScreen.forEachIndexed { index, preference ->
+            // Index 0 is the hint
+            if (index != 0) {
+                val data = prefs.getTileData(index)
+                val context = preference.context
+                preference.summary = data?.tileLabel ?: getString(R.string.tile_disabled)
+                preference.icon = if (data == null) {
+                    null
+                } else {
+                    ContextCompat.getDrawable(context, AbstractTileService.getIconRes(context, data.icon))?.apply {
+                        mutate()
+                        setTint(context.getColor(R.color.pref_icon_grey))
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/TileSettingsFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/TileSettingsFragment.kt
new file mode 100644
index 0000000000..5b74235fa1
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/TileSettingsFragment.kt
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2010-2022 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.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import androidx.core.content.ContextCompat
+import androidx.core.content.edit
+import androidx.core.os.bundleOf
+import androidx.preference.EditTextPreference
+import androidx.preference.ListPreference
+import androidx.preference.SwitchPreferenceCompat
+import com.google.android.material.snackbar.Snackbar
+import org.openhab.habdroid.R
+import org.openhab.habdroid.background.tiles.AbstractTileService
+import org.openhab.habdroid.background.tiles.TileData
+import org.openhab.habdroid.background.tiles.getTileData
+import org.openhab.habdroid.background.tiles.putTileData
+import org.openhab.habdroid.model.Item
+import org.openhab.habdroid.ui.BasicItemPickerActivity
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+import org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
+import org.openhab.habdroid.ui.preference.widgets.ItemAndStatePreference
+
+@RequiresApi(Build.VERSION_CODES.N)
+class TileSettingsFragment :
+    PreferencesActivity.AbstractSettingsFragment(),
+    PreferencesActivity.ConfirmLeaveDialogFragment.Callback {
+    override val titleResId: Int @StringRes get() = R.string.tile
+    private var tileId = 0
+
+    private lateinit var enabledPref: SwitchPreferenceCompat
+    private lateinit var itemAndStatePref: ItemAndStatePreference
+    private lateinit var namePref: CustomInputTypePreference
+    private lateinit var iconPref: ListPreference
+    private lateinit var requireUnlockPref: SwitchPreferenceCompat
+    private var itemAndStatePrefCallback = registerForActivityResult(
+        ActivityResultContracts.StartActivityForResult()
+    ) { result ->
+        Log.d(TAG, "itemAndStatePrefCallback: $result")
+        val data = result.data ?: return@registerForActivityResult
+        itemAndStatePref.item = data.getStringExtra("item")
+        itemAndStatePref.label = data.getStringExtra("label")
+        itemAndStatePref.state = data.getStringExtra("state")
+        itemAndStatePref.mappedState = data.getStringExtra("mappedState")
+        val itemTags = data.extras?.getParcelableArray("tags", Item.Tag::class.java)
+        updateItemAndStatePrefSummary()
+
+        if (namePref.text.isNullOrEmpty()) {
+            namePref.text = itemAndStatePref.label
+        }
+        if (iconPref.value == null || iconPref.value == getString(R.string.tile_icon_openhab_value)) {
+            val selectedIcon = data.getStringExtra("icon") ?: "openhab_icon"
+            val preSelectIcon = if (selectedIcon.startsWith("parents")) {
+                R.string.tile_icon_people_value
+            } else if (selectedIcon.startsWith("boy") || selectedIcon.startsWith("girl")) {
+                R.string.tile_icon_child_value
+            } else if (selectedIcon.startsWith("baby")) {
+                R.string.tile_icon_baby_value
+            } else if (selectedIcon.startsWith("man")) {
+                R.string.tile_icon_man_value
+            } else if (selectedIcon.startsWith("women")) {
+                R.string.tile_icon_woman_value
+            } else {
+                when (selectedIcon) {
+                    "screen" -> R.string.tile_icon_tv_value
+                    "lightbulb", "light", "slider" -> R.string.tile_icon_bulb_value
+                    "lock" -> R.string.tile_icon_lock_value
+                    "time" -> R.string.tile_icon_clock_value
+                    "house", "presence", "group" -> R.string.tile_icon_house_value
+                    "microphone", "recorder" -> R.string.tile_icon_microphone_value
+                    "colorpicker", "colorlight", "colorwheel", "rbg" -> R.string.tile_icon_color_palette_value
+                    "battery", "batterylevel", "lowbattery" -> R.string.tile_icon_battery_value
+                    "zoom" -> R.string.tile_icon_magnifier_value
+                    "garden" -> R.string.tile_icon_tree_value
+                    "network" -> R.string.tile_icon_wifi_value
+                    "shield" -> R.string.tile_icon_shield_value
+                    "fan", "fan_box", "fan_ceiling" -> R.string.tile_icon_fan_value
+                    "bedroom", "bedroom_blue", "bedroom_orange", "bedroom_red" -> R.string.tile_icon_bed_value
+                    "settings" -> R.string.tile_icon_settings_value
+                    "bath", "toilet" -> R.string.tile_icon_bath_value
+                    "blinds", "rollershutter" -> R.string.tile_icon_roller_shutter_value
+                    "camera" -> R.string.tile_icon_camera_value
+                    "wallswitch" -> R.string.tile_icon_light_switch_value
+                    "garage", "garagedoor", "garage_detached", "garage_detached_selected" ->
+                        R.string.tile_icon_garage_value
+                    "switch" -> R.string.tile_icon_switch_value
+                    "text" -> R.string.tile_icon_text_value
+                    "sofa" -> R.string.tile_icon_sofa_value
+                    else -> when {
+                        itemTags?.contains(Item.Tag.Light) == true -> R.string.tile_icon_bulb_value
+                        itemTags?.contains(Item.Tag.Blinds) == true -> R.string.tile_icon_roller_shutter_value
+                        itemTags?.contains(Item.Tag.Switch) == true -> R.string.tile_icon_switch_value
+                        else -> R.string.tile_icon_openhab_value
+                    }
+                }
+            }
+            iconPref.value = getString(preSelectIcon)
+            updateIconPrefIcon()
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        tileId = arguments?.getInt("id") ?: throw AssertionError("No tile id specified")
+        setHasOptionsMenu(true)
+
+        setDataFromPrefs()
+
+        val backCallback = object : OnBackPressedCallback(true) {
+            override fun handleOnBackPressed() {
+                if (prefs.getTileData(tileId) != getCurrentPrefsAsTileData()) {
+                    PreferencesActivity.ConfirmLeaveDialogFragment().show(childFragmentManager, "dialog_confirm_leave")
+                } else {
+                    isEnabled = false
+                    parentActivity.onBackPressedDispatcher.onBackPressed()
+                }
+            }
+        }
+
+        parentActivity.onBackPressedDispatcher.addCallback(this, backCallback)
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        super.onCreateOptionsMenu(menu, inflater)
+        inflater.inflate(R.menu.prefs_save, menu)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        return when (item.itemId) {
+            R.id.save -> {
+                onLeaveAndSave()
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(R.xml.preferences_tile)
+        enabledPref = findPreference("tile_show")!!
+        itemAndStatePref = findPreference("tile_item_and_action")!!
+        namePref = findPreference("tile_name")!!
+        iconPref = findPreference("tile_icon")!!
+        requireUnlockPref = findPreference("tile_require_unlock")!!
+
+        namePref.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
+        itemAndStatePref.setOnPreferenceClickListener {
+            val intent = Intent(it.context, BasicItemPickerActivity::class.java)
+            intent.putExtra("item", itemAndStatePref.item)
+            itemAndStatePrefCallback.launch(intent)
+            true
+        }
+    }
+
+    private fun updateItemAndStatePrefSummary() {
+        itemAndStatePref.summary = if (itemAndStatePref.label == null) {
+            itemAndStatePref.context.getString(R.string.info_not_set)
+        } else {
+            "${itemAndStatePref.label} (${itemAndStatePref.item}): ${itemAndStatePref.mappedState}"
+        }
+    }
+
+    private fun updateIconPrefIcon(newIcon: String = iconPref.value) {
+        val context = iconPref.context
+        iconPref.icon =
+            ContextCompat.getDrawable(context, AbstractTileService.getIconRes(context, newIcon))?.apply {
+                mutate()
+                setTint(context.getColor(R.color.pref_icon_grey))
+            }
+    }
+
+    private fun getCurrentPrefsAsTileData(): TileData? {
+        return if (enabledPref.isChecked) {
+            TileData(
+                item = itemAndStatePref.item.orEmpty(),
+                state = itemAndStatePref.state.orEmpty(),
+                label = itemAndStatePref.label.orEmpty(),
+                tileLabel = namePref.text.orEmpty(),
+                mappedState = itemAndStatePref.mappedState.orEmpty(),
+                icon = iconPref.value.orEmpty(),
+                requireUnlock = requireUnlockPref.isChecked
+            )
+        } else {
+            null
+        }
+    }
+
+    private fun setDataFromPrefs() {
+        val data = prefs.getTileData(tileId)
+        enabledPref.isChecked = data != null
+        @Suppress("SpellCheckingInspection")
+        if (data != null) {
+            itemAndStatePref.item = data.item
+            itemAndStatePref.label = data.label
+            itemAndStatePref.state = data.state
+            itemAndStatePref.mappedState = data.mappedState
+            namePref.text = data.tileLabel
+            iconPref.value = data.icon
+            requireUnlockPref.isChecked = data.requireUnlock
+        }
+        iconPref.setOnPreferenceChangeListener { _, newValue ->
+            updateIconPrefIcon(newValue as String)
+            true
+        }
+        updateIconPrefIcon()
+        updateItemAndStatePrefSummary()
+    }
+
+    override fun onLeaveAndSave() {
+        Log.d(TAG, "Save tile $tileId")
+        val context = preferenceManager.context
+        val currentData = getCurrentPrefsAsTileData()
+        if (currentData != null && !currentData.isValid()) {
+            parentActivity.showSnackbar(
+                PreferencesActivity.SNACKBAR_TAG_MISSING_PREFS,
+                R.string.error_missing_prefs,
+                Snackbar.LENGTH_LONG
+            )
+            return
+        }
+
+        prefs.edit {
+            putTileData(tileId, currentData)
+        }
+        AbstractTileService.requestTileUpdate(context, tileId)
+
+        parentActivity.onBackPressedDispatcher.onBackPressed()
+    }
+
+    override fun onLeaveAndDiscard() {
+        setDataFromPrefs()
+        parentActivity.onBackPressedDispatcher.onBackPressed()
+    }
+
+    companion object {
+        private val TAG = TileSettingsFragment::class.java.simpleName
+
+        fun newInstance(id: Int): TileSettingsFragment {
+            val f = TileSettingsFragment()
+            val args = bundleOf("id" to id)
+            f.arguments = args
+            return f
+        }
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/WidgetSettingsFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/WidgetSettingsFragment.kt
new file mode 100644
index 0000000000..651718016a
--- /dev/null
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/fragments/WidgetSettingsFragment.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2010-2022 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.appwidget.AppWidgetManager
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
+import androidx.core.os.bundleOf
+import androidx.preference.EditTextPreference
+import androidx.preference.ListPreference
+import androidx.preference.SwitchPreference
+import com.google.android.material.snackbar.Snackbar
+import org.openhab.habdroid.R
+import org.openhab.habdroid.background.BackgroundTasksManager
+import org.openhab.habdroid.model.toOH2IconResource
+import org.openhab.habdroid.ui.BasicItemPickerActivity
+import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget
+import org.openhab.habdroid.ui.preference.PreferencesActivity
+import org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
+import org.openhab.habdroid.ui.preference.widgets.ItemAndStatePreference
+import org.openhab.habdroid.util.CacheManager
+import org.openhab.habdroid.util.PrefKeys
+
+class WidgetSettingsFragment :
+    PreferencesActivity.AbstractSettingsFragment(),
+    PreferencesActivity.ConfirmLeaveDialogFragment.Callback {
+    override val titleResId: Int @StringRes get() = R.string.item_update_widget
+    private var widgetId: Int = AppWidgetManager.INVALID_APPWIDGET_ID
+
+    private lateinit var itemAndStatePref: ItemAndStatePreference
+    private lateinit var namePref: CustomInputTypePreference
+    private lateinit var showStatePref: SwitchPreference
+    private lateinit var themePref: ListPreference
+    private var itemAndStatePrefCallback = registerForActivityResult(
+        ActivityResultContracts.StartActivityForResult()
+    ) { result ->
+        Log.d(TAG, "itemAndStatePrefCallback: $result")
+        val data = result.data ?: return@registerForActivityResult
+        itemAndStatePref.item = data.getStringExtra("item")
+        val label = if (data.getStringExtra("label").isNullOrEmpty()) {
+            itemAndStatePref.item
+        } else {
+            data.getStringExtra("label")
+        }
+        itemAndStatePref.label = label
+        itemAndStatePref.state = data.getStringExtra("state")
+        itemAndStatePref.mappedState = data.getStringExtra("mappedState")
+        itemAndStatePref.icon = data.getStringExtra("icon")
+        updateItemAndStatePrefSummary()
+
+        if (namePref.text.isNullOrEmpty()) {
+            namePref.text = preferenceManager.context.getString(
+                R.string.item_update_widget_text,
+                itemAndStatePref.label,
+                itemAndStatePref.mappedState
+            )
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setHasOptionsMenu(true)
+        activity?.setResult(AppCompatActivity.RESULT_CANCELED)
+
+        widgetId = requireArguments().getInt("id", AppWidgetManager.INVALID_APPWIDGET_ID)
+        if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+            throw IllegalArgumentException("$TAG called with INVALID_APPWIDGET_ID")
+        }
+
+        setDataFromPrefs()
+
+        val backCallback = object : OnBackPressedCallback(true) {
+            override fun handleOnBackPressed() {
+                val oldData = ItemUpdateWidget.getInfoForWidget(requireContext(), widgetId)
+                val newData = getCurrentPrefsAsWidgetData()
+                if (oldData != newData) {
+                    PreferencesActivity.ConfirmLeaveDialogFragment().show(childFragmentManager, "dialog_confirm_leave")
+                } else {
+                    isEnabled = false
+                    parentActivity.onBackPressedDispatcher.onBackPressed()
+                }
+            }
+        }
+
+        parentActivity.onBackPressedDispatcher.addCallback(this, backCallback)
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        super.onCreateOptionsMenu(menu, inflater)
+        inflater.inflate(R.menu.prefs_save, menu)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        return when (item.itemId) {
+            R.id.save -> {
+                onLeaveAndSave()
+                true
+            }
+            else -> super.onOptionsItemSelected(item)
+        }
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        addPreferencesFromResource(R.xml.preferences_homescreen_widget)
+        itemAndStatePref = findPreference("widget_item_and_action")!!
+        namePref = findPreference("widget_name")!!
+        showStatePref = findPreference("show_state")!!
+        themePref = findPreference("widget_theme")!!
+
+        namePref.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
+        itemAndStatePref.setOnPreferenceClickListener {
+            val intent = Intent(it.context, BasicItemPickerActivity::class.java)
+            intent.putExtra("item", itemAndStatePref.item)
+            intent.putExtra("show_no_command", true)
+            itemAndStatePrefCallback.launch(intent)
+            true
+        }
+    }
+
+    private fun updateItemAndStatePrefSummary() {
+        itemAndStatePref.summary = if (itemAndStatePref.item.isNullOrEmpty()) {
+            itemAndStatePref.context.getString(R.string.info_not_set)
+        } else {
+            "${itemAndStatePref.label} (${itemAndStatePref.item}): ${itemAndStatePref.mappedState}"
+        }
+    }
+
+    private fun getCurrentPrefsAsWidgetData(): ItemUpdateWidget.ItemUpdateWidgetData {
+        return ItemUpdateWidget.ItemUpdateWidgetData(
+            item = itemAndStatePref.item.orEmpty(),
+            command = itemAndStatePref.state,
+            label = itemAndStatePref.label.orEmpty(),
+            widgetLabel = namePref.text.orEmpty(),
+            mappedState = itemAndStatePref.mappedState.orEmpty(),
+            icon = itemAndStatePref.icon.toOH2IconResource(),
+            theme = themePref.value,
+            showState = showStatePref.isChecked
+        )
+    }
+
+    private fun setDataFromPrefs() {
+        val data = ItemUpdateWidget.getInfoForWidget(requireContext(), widgetId)
+
+        itemAndStatePref.item = data.item
+        itemAndStatePref.label = data.label
+        itemAndStatePref.state = data.command
+        itemAndStatePref.mappedState = data.mappedState
+        itemAndStatePref.icon = data.icon?.icon
+        namePref.text = data.widgetLabel
+        showStatePref.isChecked = data.showState
+        themePref.value = data.theme
+
+        updateItemAndStatePrefSummary()
+    }
+
+    override fun onLeaveAndSave() {
+        Log.d(TAG, "Save widget $widgetId")
+        val context = preferenceManager.context
+        val newData = getCurrentPrefsAsWidgetData()
+        if (!newData.isValid()) {
+            parentActivity.showSnackbar(
+                PreferencesActivity.SNACKBAR_TAG_MISSING_PREFS,
+                R.string.error_missing_prefs,
+                Snackbar.LENGTH_LONG
+            )
+            return
+        }
+
+        val oldData = ItemUpdateWidget.getInfoForWidget(context, widgetId)
+        if (oldData.icon != newData.icon) {
+            CacheManager.getInstance(context).removeWidgetIcon(widgetId)
+        }
+
+        ItemUpdateWidget.saveInfoForWidget(context, newData, widgetId)
+        prefs.edit {
+            putString(PrefKeys.LAST_WIDGET_THEME, newData.theme)
+        }
+
+        BackgroundTasksManager.schedulePeriodicTrigger(context, false)
+
+        val updateIntent = Intent(context, ItemUpdateWidget::class.java).apply {
+            action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
+            putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(widgetId))
+        }
+        context.sendBroadcast(updateIntent)
+
+        val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
+        activity?.setResult(AppCompatActivity.RESULT_OK, resultValue)
+
+        parentActivity.onBackPressedDispatcher.onBackPressed()
+    }
+
+    override fun onLeaveAndDiscard() {
+        setDataFromPrefs()
+        parentActivity.onBackPressedDispatcher.onBackPressed()
+    }
+
+    companion object {
+        private val TAG = WidgetSettingsFragment::class.java.simpleName
+
+        fun newInstance(id: Int): WidgetSettingsFragment {
+            val f = WidgetSettingsFragment()
+            val args = bundleOf("id" to id)
+            f.arguments = args
+            return f
+        }
+    }
+}
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/ChartScalingPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ChartScalingPreference.kt
similarity index 98%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/ChartScalingPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ChartScalingPreference.kt
index 38eab38af1..42cc70bc7b 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/ChartScalingPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ChartScalingPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.content.res.TypedArray
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/CustomInputTypePreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/CustomInputTypePreference.kt
similarity index 98%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/CustomInputTypePreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/CustomInputTypePreference.kt
index d960d0c276..bdddf5e06c 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/CustomInputTypePreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/CustomInputTypePreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.os.Build
@@ -20,7 +20,6 @@ import android.text.TextWatcher
 import android.util.AttributeSet
 import android.view.View
 import android.widget.ArrayAdapter
-import android.widget.EditText
 import androidx.core.os.bundleOf
 import androidx.fragment.app.DialogFragment
 import androidx.preference.EditTextPreference
@@ -29,7 +28,7 @@ import androidx.preference.PreferenceDataStore
 import com.google.android.material.textfield.MaterialAutoCompleteTextView
 import com.google.android.material.textfield.TextInputLayout
 import org.openhab.habdroid.R
-import org.openhab.habdroid.ui.CustomDialogPreference
+import org.openhab.habdroid.ui.preference.CustomDialogPreference
 
 open class CustomInputTypePreference constructor(context: Context, attrs: AttributeSet) :
     EditTextPreference(context, attrs), CustomDialogPreference {
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/DeviceIdentifierPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/DeviceIdentifierPreference.kt
similarity index 98%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/DeviceIdentifierPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/DeviceIdentifierPreference.kt
index 51543ebe36..4e7fca8709 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/DeviceIdentifierPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/DeviceIdentifierPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.os.Build
@@ -31,7 +31,7 @@ import androidx.preference.PreferenceDialogFragmentCompat
 import com.google.android.material.switchmaterial.SwitchMaterial
 import com.google.android.material.textfield.TextInputLayout
 import org.openhab.habdroid.R
-import org.openhab.habdroid.ui.CustomDialogPreference
+import org.openhab.habdroid.ui.preference.CustomDialogPreference
 import org.openhab.habdroid.util.PrefKeys
 
 class DeviceIdentifierPreference constructor(context: Context, attrs: AttributeSet) : DialogPreference(context, attrs),
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/InfoPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/InfoPreference.kt
similarity index 97%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/InfoPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/InfoPreference.kt
index 9d9c0b1faa..3d74e2082c 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/InfoPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/InfoPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.util.AttributeSet
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/ItemAndStatePreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ItemAndStatePreference.kt
similarity index 93%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/ItemAndStatePreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ItemAndStatePreference.kt
index f12be7604e..987978c0d0 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/ItemAndStatePreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ItemAndStatePreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.util.AttributeSet
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/ItemUpdatingPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ItemUpdatingPreference.kt
similarity index 98%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/ItemUpdatingPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ItemUpdatingPreference.kt
index e593eb3d9d..9c8ce8fc5c 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/ItemUpdatingPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/ItemUpdatingPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.content.res.TypedArray
@@ -41,7 +41,7 @@ import java.text.DateFormat
 import org.openhab.habdroid.R
 import org.openhab.habdroid.background.BackgroundTasksManager
 import org.openhab.habdroid.background.ItemUpdateWorker
-import org.openhab.habdroid.ui.CustomDialogPreference
+import org.openhab.habdroid.ui.preference.CustomDialogPreference
 import org.openhab.habdroid.ui.setupHelpIcon
 import org.openhab.habdroid.ui.updateHelpIconAlpha
 import org.openhab.habdroid.util.getPrefixForBgTasks
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/NotificationPollingPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/NotificationPollingPreference.kt
similarity index 97%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/NotificationPollingPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/NotificationPollingPreference.kt
index 0e5beacf86..8133182f40 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/NotificationPollingPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/NotificationPollingPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.content.SharedPreferences
@@ -32,7 +32,7 @@ import androidx.preference.DialogPreference
 import androidx.preference.PreferenceDialogFragmentCompat
 import org.openhab.habdroid.R
 import org.openhab.habdroid.core.CloudMessagingHelper
-import org.openhab.habdroid.ui.CustomDialogPreference
+import org.openhab.habdroid.ui.preference.CustomDialogPreference
 import org.openhab.habdroid.ui.setupHelpIcon
 import org.openhab.habdroid.ui.updateHelpIconAlpha
 import org.openhab.habdroid.util.PrefKeys
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/PrimaryServerPreference.kt
similarity index 96%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/PrimaryServerPreference.kt
index 615e83dc55..ec45bf0e64 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/PrimaryServerPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.util.AttributeSet
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/SslClientCertificatePreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/SslClientCertificatePreference.kt
similarity index 97%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/SslClientCertificatePreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/SslClientCertificatePreference.kt
index f218aa0bb9..9038ff3409 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/SslClientCertificatePreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/SslClientCertificatePreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.annotation.SuppressLint
 import android.app.Activity
@@ -34,7 +34,7 @@ import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import org.openhab.habdroid.R
-import org.openhab.habdroid.ui.PreferencesActivity
+import org.openhab.habdroid.ui.preference.PreferencesActivity
 import org.openhab.habdroid.ui.setupHelpIcon
 import org.openhab.habdroid.ui.updateHelpIconAlpha
 
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/UrlInputPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/UrlInputPreference.kt
similarity index 99%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/UrlInputPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/UrlInputPreference.kt
index d214db7b14..144a63f237 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/UrlInputPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/UrlInputPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.os.Build
diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/WifiSsidInputPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/WifiSsidInputPreference.kt
similarity index 97%
rename from mobile/src/main/java/org/openhab/habdroid/ui/preference/WifiSsidInputPreference.kt
rename to mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/WifiSsidInputPreference.kt
index 725ec20a5e..43cd2b11a4 100644
--- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/WifiSsidInputPreference.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/widgets/WifiSsidInputPreference.kt
@@ -11,7 +11,7 @@
  * SPDX-License-Identifier: EPL-2.0
  */
 
-package org.openhab.habdroid.ui.preference
+package org.openhab.habdroid.ui.preference.widgets
 
 import android.content.Context
 import android.os.Build
@@ -31,7 +31,7 @@ import com.google.android.material.textfield.TextInputLayout
 import org.openhab.habdroid.R
 import org.openhab.habdroid.core.OpenHabApplication
 import org.openhab.habdroid.model.toWifiSsids
-import org.openhab.habdroid.ui.CustomDialogPreference
+import org.openhab.habdroid.ui.preference.CustomDialogPreference
 import org.openhab.habdroid.util.getCurrentWifiSsid
 
 class WifiSsidInputPreference constructor(context: Context, attrs: AttributeSet) :
diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt
index 6d25d6570f..b26c89924e 100644
--- a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt
+++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt
@@ -30,7 +30,7 @@ import org.openhab.habdroid.model.IconFormat
 import org.openhab.habdroid.model.ServerConfiguration
 import org.openhab.habdroid.model.ServerProperties
 import org.openhab.habdroid.model.Sitemap
-import org.openhab.habdroid.ui.preference.toItemUpdatePrefValue
+import org.openhab.habdroid.ui.preference.widgets.toItemUpdatePrefValue
 import org.openhab.habdroid.ui.widget.WidgetImageView
 
 enum class ScreenLockMode {
diff --git a/mobile/src/main/res/xml/item_update_widget.xml b/mobile/src/main/res/xml/item_update_widget.xml
index 0f288c234f..17cf040fdc 100644
--- a/mobile/src/main/res/xml/item_update_widget.xml
+++ b/mobile/src/main/res/xml/item_update_widget.xml
@@ -6,6 +6,6 @@
     android:previewImage="@drawable/widget_preview_item_update"
     android:initialLayout="@layout/widget_item_update_dark_text"
     android:widgetCategory="home_screen|keyguard"
-    android:configure="org.openhab.habdroid.ui.PreferencesActivity"
+    android:configure="org.openhab.habdroid.ui.preference.PreferencesActivity"
     android:resizeMode="horizontal|vertical" >
 </appwidget-provider>
diff --git a/mobile/src/main/res/xml/preferences.xml b/mobile/src/main/res/xml/preferences.xml
index b33d06da6b..da9db79584 100644
--- a/mobile/src/main/res/xml/preferences.xml
+++ b/mobile/src/main/res/xml/preferences.xml
@@ -37,7 +37,7 @@
             android:title="@string/settings_image_widget_scaling"
             android:summaryOff="@string/settings_image_widget_scaling_summary_off"
             android:summaryOn="@string/settings_image_widget_scaling_summary_on" />
-        <org.openhab.habdroid.ui.preference.ChartScalingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ChartScalingPreference
             android:title="@string/settings_chart_scaling"
             android:summary="@string/settings_chart_scaling_summary"
             android:key="chartScalingFactor"
@@ -96,7 +96,7 @@
             android:enabled="false"
             android:selectable="false"
             android:key="notification_status" />
-        <org.openhab.habdroid.ui.preference.NotificationPollingPreference
+        <org.openhab.habdroid.ui.preference.widgets.NotificationPollingPreference
             android:defaultValue="false"
             android:key="foss_notifications_enabled"
             android:title="@string/push_notification_status" />
@@ -120,7 +120,7 @@
             android:icon="@drawable/ic_bell_outline_grey_24dp" />
     </PreferenceCategory>
     <PreferenceCategory android:title="@string/settings_misc_title">
-        <org.openhab.habdroid.ui.preference.DeviceIdentifierPreference
+        <org.openhab.habdroid.ui.preference.widgets.DeviceIdentifierPreference
             android:defaultValue="@string/empty_string"
             android:key="sendDeviceInfoPrefix"
             android:icon="@drawable/ic_tag_text_outline_grey_24dp"
diff --git a/mobile/src/main/res/xml/preferences_device_information.xml b/mobile/src/main/res/xml/preferences_device_information.xml
index 468dfb681f..974c430d64 100644
--- a/mobile/src/main/res/xml/preferences_device_information.xml
+++ b/mobile/src/main/res/xml/preferences_device_information.xml
@@ -3,13 +3,13 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:title="@string/send_device_info_to_server">
-    <org.openhab.habdroid.ui.preference.InfoPreference
+    <org.openhab.habdroid.ui.preference.widgets.InfoPreference
         android:key="device_identifier_prefix_background_tasks" />
     <PreferenceCategory
         android:title="@string/send_device_info_category_event_based_title">
-        <org.openhab.habdroid.ui.preference.InfoPreference
+        <org.openhab.habdroid.ui.preference.widgets.InfoPreference
             android:summary="@string/send_device_info_category_event_based_explanation" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|AlarmClock"
             android:key="alarmClock"
             app:helpUrl="@string/settings_alarm_clock_howto_url"
@@ -18,7 +18,7 @@
             app:summaryDisabled="@string/settings_alarm_clock_summary_off"
             app:iconEnabled="@drawable/ic_alarm_grey_24dp"
             app:iconDisabled="@drawable/ic_alarm_off_grey_24dp" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|CallState"
             android:key="phoneState"
             app:helpUrl="@string/settings_phone_state_howto_url"
@@ -27,7 +27,7 @@
             android:title="@string/settings_phone_state"
             app:iconEnabled="@drawable/ic_phone_outline_grey_24dp"
             app:iconDisabled="@drawable/ic_phone_off_outline_grey_24dp" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|BluetoothDevices"
             android:key="send_bluetooth_devices"
             app:helpUrl="https://www.openhab.org/docs/apps/android.html#bluetooth-devices"
@@ -36,7 +36,7 @@
             android:title="@string/settings_bluetooth_devices"
             app:iconEnabled="@drawable/ic_baseline_bluetooth_grey_24dp"
             app:iconDisabled="@drawable/ic_baseline_bluetooth_disabled_grey_24dp" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|Gadgetbridge"
             android:key="send_gadgetbridge"
             app:helpUrl="@string/settings_gadgetbridge_howto_url"
@@ -48,7 +48,7 @@
     </PreferenceCategory>
     <PreferenceCategory
         android:title="@string/send_device_info_category_schedule_based_title">
-        <org.openhab.habdroid.ui.preference.InfoPreference
+        <org.openhab.habdroid.ui.preference.widgets.InfoPreference
             android:summary="@string/send_device_info_category_schedule_based_explanation" />
         <ListPreference
             android:key="send_device_info_schedule"
@@ -62,7 +62,7 @@
             android:title="@string/send_device_info_foreground_service_title"
             android:summary="@string/send_device_info_foreground_service_summary"
             android:defaultValue="false" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|BatteryLevel"
             android:key="battery_level"
             app:helpUrl="@string/settings_battery_level_howto_url"
@@ -71,7 +71,7 @@
             android:title="@string/settings_battery_level"
             app:iconEnabled="@drawable/ic_battery_outline_grey_24dp"
             app:iconDisabled="@drawable/ic_battery_off_outline_grey_24dp" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|ChargingState"
             android:key="charging_state"
             app:helpUrl="@string/settings_charging_state_howto_url"
@@ -80,7 +80,7 @@
             android:title="@string/settings_charging_state"
             app:iconEnabled="@drawable/ic_power_plug_outline_grey_24dp"
             app:iconDisabled="@drawable/ic_power_plug_off_outline_grey_24dp" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|WifiName"
             android:key="send_wifi_ssid"
             app:helpUrl="@string/settings_wifi_ssid_howto_url"
@@ -89,7 +89,7 @@
             android:title="@string/settings_wifi_ssid"
             app:iconEnabled="@drawable/ic_wifi_strength_outline_grey_24dp"
             app:iconDisabled="@drawable/ic_wifi_strength_off_outline_grey_24dp" />
-        <org.openhab.habdroid.ui.preference.ItemUpdatingPreference
+        <org.openhab.habdroid.ui.preference.widgets.ItemUpdatingPreference
             android:defaultValue="false|DndMode"
             android:key="send_dnd_mode"
             app:helpUrl="@string/settings_dnd_mode_howto_url"
diff --git a/mobile/src/main/res/xml/preferences_homescreen_widget.xml b/mobile/src/main/res/xml/preferences_homescreen_widget.xml
index 0829340dcc..c355d16096 100644
--- a/mobile/src/main/res/xml/preferences_homescreen_widget.xml
+++ b/mobile/src/main/res/xml/preferences_homescreen_widget.xml
@@ -2,11 +2,11 @@
 <androidx.preference.PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
-    <org.openhab.habdroid.ui.preference.ItemAndStatePreference
+    <org.openhab.habdroid.ui.preference.widgets.ItemAndStatePreference
         android:persistent="false"
         android:key="widget_item_and_action"
         android:title="@string/tile_item_and_action" />
-    <org.openhab.habdroid.ui.preference.CustomInputTypePreference
+    <org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
         android:persistent="false"
         android:key="widget_name"
         android:title="@string/tile_name"
diff --git a/mobile/src/main/res/xml/preferences_local_connection.xml b/mobile/src/main/res/xml/preferences_local_connection.xml
index fbfabce6a3..0906a2a5fe 100644
--- a/mobile/src/main/res/xml/preferences_local_connection.xml
+++ b/mobile/src/main/res/xml/preferences_local_connection.xml
@@ -3,7 +3,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:title="@string/settings_openhab_connection">
-    <org.openhab.habdroid.ui.preference.UrlInputPreference
+    <org.openhab.habdroid.ui.preference.widgets.UrlInputPreference
         android:defaultValue="@string/empty_string"
         android:key="url"
         android:persistent="false"
@@ -11,7 +11,7 @@
         android:title="@string/settings_openhab_url"
         android:icon="@drawable/ic_earth_grey_24dp"
         app:isForRemoteServer="false" />
-    <org.openhab.habdroid.ui.preference.CustomInputTypePreference
+    <org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
         android:defaultValue="@string/empty_string"
         android:persistent="false"
         android:inputType="textNoSuggestions"
@@ -20,7 +20,7 @@
         android:autofillHints="username,emailAddress"
         app:whitespaceBehavior="trim"
         android:icon="@drawable/ic_person_outline_grey_24dp" />
-    <org.openhab.habdroid.ui.preference.CustomInputTypePreference
+    <org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
         android:defaultValue="@string/empty_string"
         android:persistent="false"
         android:inputType="textPassword"
@@ -29,7 +29,7 @@
         android:autofillHints="password"
         app:whitespaceBehavior="trim"
         android:icon="@drawable/ic_shield_key_outline_grey_24dp" />
-    <org.openhab.habdroid.ui.preference.InfoPreference
+    <org.openhab.habdroid.ui.preference.widgets.InfoPreference
         android:key="api_token_hint"
         app:infoUrl="https://www.openhab.org/docs/configuration/restdocs.html#authentication"
         android:summary="@string/settings_openhab_api_token_hint" />
diff --git a/mobile/src/main/res/xml/preferences_remote_connection.xml b/mobile/src/main/res/xml/preferences_remote_connection.xml
index cbea977d54..1d4b63897b 100644
--- a/mobile/src/main/res/xml/preferences_remote_connection.xml
+++ b/mobile/src/main/res/xml/preferences_remote_connection.xml
@@ -3,7 +3,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:title="@string/settings_openhab_alt_connection">
-    <org.openhab.habdroid.ui.preference.UrlInputPreference
+    <org.openhab.habdroid.ui.preference.widgets.UrlInputPreference
         android:defaultValue="@string/empty_string"
         android:key="url"
         android:persistent="false"
@@ -11,7 +11,7 @@
         android:title="@string/settings_openhab_alturl"
         android:icon="@drawable/ic_earth_grey_24dp"
         app:isForRemoteServer="true" />
-    <org.openhab.habdroid.ui.preference.CustomInputTypePreference
+    <org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
         android:defaultValue="@string/empty_string"
         android:persistent="false"
         android:inputType="textNoSuggestions"
@@ -20,7 +20,7 @@
         android:autofillHints="username,emailAddress"
         app:whitespaceBehavior="trim"
         android:icon="@drawable/ic_person_outline_grey_24dp" />
-    <org.openhab.habdroid.ui.preference.CustomInputTypePreference
+    <org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
         android:defaultValue="@string/empty_string"
         android:persistent="false"
         android:inputType="textPassword"
@@ -29,7 +29,7 @@
         android:autofillHints="password"
         app:whitespaceBehavior="trim"
         android:icon="@drawable/ic_shield_key_outline_grey_24dp" />
-    <org.openhab.habdroid.ui.preference.InfoPreference
+    <org.openhab.habdroid.ui.preference.widgets.InfoPreference
         android:key="api_token_hint"
         app:infoUrl="https://www.openhab.org/docs/configuration/restdocs.html#authentication"
         android:summary="@string/settings_openhab_api_token_hint" />
diff --git a/mobile/src/main/res/xml/preferences_server.xml b/mobile/src/main/res/xml/preferences_server.xml
index df56392e84..1e1fb150a2 100644
--- a/mobile/src/main/res/xml/preferences_server.xml
+++ b/mobile/src/main/res/xml/preferences_server.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
-    <org.openhab.habdroid.ui.preference.CustomInputTypePreference
+    <org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
         android:key="name"
         android:title="@string/settings_server_name"
         android:persistent="false"
@@ -22,7 +22,7 @@
         android:clickable="true"
         android:key="clear_default_sitemap"
         android:title="@string/settings_clear_default_sitemap" />
-    <org.openhab.habdroid.ui.preference.PrimaryServerPreference
+    <org.openhab.habdroid.ui.preference.widgets.PrimaryServerPreference
         android:key="primary_server_pref"
         android:icon="@drawable/ic_star_border_grey_24dp"
         android:title="@string/settings_server_primary_title" />
@@ -32,12 +32,12 @@
         android:key="advanced_prefs"
         android:icon="@drawable/ic_keyboard_arrow_down_grey_24dp"
         android:title="@string/settings_advanced" />
-    <org.openhab.habdroid.ui.preference.SslClientCertificatePreference
+    <org.openhab.habdroid.ui.preference.widgets.SslClientCertificatePreference
         android:key="clientcert"
         android:persistent="false"
         app:isPreferenceVisible="false"
         android:title="@string/settings_openhab_sslclientcert" />
-    <org.openhab.habdroid.ui.preference.WifiSsidInputPreference
+    <org.openhab.habdroid.ui.preference.widgets.WifiSsidInputPreference
         android:key="wifi_ssid"
         android:persistent="false"
         app:isPreferenceVisible="false"
diff --git a/mobile/src/main/res/xml/preferences_tile.xml b/mobile/src/main/res/xml/preferences_tile.xml
index 08698d9957..ecfb90e73a 100644
--- a/mobile/src/main/res/xml/preferences_tile.xml
+++ b/mobile/src/main/res/xml/preferences_tile.xml
@@ -6,12 +6,12 @@
         android:persistent="false"
         android:key="tile_show"
         android:title="@string/tile_enable" />
-    <org.openhab.habdroid.ui.preference.ItemAndStatePreference
+    <org.openhab.habdroid.ui.preference.widgets.ItemAndStatePreference
         android:persistent="false"
         android:dependency="tile_show"
         android:key="tile_item_and_action"
         android:title="@string/tile_item_and_action" />
-    <org.openhab.habdroid.ui.preference.CustomInputTypePreference
+    <org.openhab.habdroid.ui.preference.widgets.CustomInputTypePreference
         android:persistent="false"
         android:dependency="tile_show"
         android:key="tile_name"
diff --git a/mobile/src/main/res/xml/preferences_tile_overview.xml b/mobile/src/main/res/xml/preferences_tile_overview.xml
index ce6b245733..186d71762a 100644
--- a/mobile/src/main/res/xml/preferences_tile_overview.xml
+++ b/mobile/src/main/res/xml/preferences_tile_overview.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.preference.PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android">
-    <org.openhab.habdroid.ui.preference.InfoPreference
+    <org.openhab.habdroid.ui.preference.widgets.InfoPreference
         android:summary="@string/tile_overview_hint" />
 </androidx.preference.PreferenceScreen>


Test patch:
diff --git a/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt b/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt
index cb6039b35f..d23fcb4566 100644
--- a/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt
+++ b/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt
@@ -18,8 +18,8 @@ import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.openhab.habdroid.model.toWifiSsids
-import org.openhab.habdroid.ui.PreferencesActivity.AbstractSettingsFragment.Companion.isWeakPassword
-import org.openhab.habdroid.ui.PreferencesActivity.ServerEditorFragment.Companion.beautifyUrl
+import org.openhab.habdroid.ui.preference.PreferencesActivity.AbstractSettingsFragment.Companion.isWeakPassword
+import org.openhab.habdroid.ui.preference.fragments.ServerEditorFragment.Companion.beautifyUrl
 
 class PreferencesUtilTest {
     @Test