From 780d9261f53c013d9291222ff7b00290f2267a37 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 14 Feb 2023 20:18:27 +0100 Subject: [PATCH] Improve SetupWizard, allow Tidepool for passing first Objective --- .../nightscout/interfaces/ConfigBuilder.kt | 43 ++ .../nightscout/interfaces/sync/Tidepool.kt | 3 + core/ui/src/main/res/values/strings.xml | 2 +- .../configBuilder/ConfigBuilderFragment.kt | 226 ++++---- .../configBuilder/ConfigBuilderPlugin.kt | 131 ++++- .../configuration/setupwizard/SWDefinition.kt | 542 ++++++++---------- .../setupwizard/elements/SWPlugin.kt | 113 +--- .../src/main/res/values/strings.xml | 2 +- .../objectives/objectives/Objective0.kt | 10 +- .../src/main/res/values/objectives.xml | 4 +- .../info/nightscout/source/RandomBgPlugin.kt | 2 +- .../dataBroadcaster/DataBroadcastPlugin.kt | 4 +- .../plugins/sync/tidepool/TidepoolPlugin.kt | 10 +- plugins/sync/src/main/res/values/strings.xml | 7 +- 14 files changed, 563 insertions(+), 536 deletions(-) create mode 100644 core/interfaces/src/main/java/info/nightscout/interfaces/sync/Tidepool.kt diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/ConfigBuilder.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/ConfigBuilder.kt index 0d8045908e..500c4566ef 100644 --- a/core/interfaces/src/main/java/info/nightscout/interfaces/ConfigBuilder.kt +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/ConfigBuilder.kt @@ -1,16 +1,59 @@ package info.nightscout.interfaces +import android.widget.LinearLayout +import androidx.annotation.StringRes +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginType interface ConfigBuilder { + /** + * Called during start of app to load configuration and start enabled plugins + */ fun initialize() + + /** + * Store current configuration to SharedPreferences + */ fun storeSettings(from: String) + + /** + * Enable another plugin and fragment and disable currently enabled if they are mutually exclusive + */ fun performPluginSwitch(changedPlugin: PluginBase, enabled: Boolean, type: PluginType) /** * Make sure plugins configuration is valid after enabling/disabling plugin */ fun processOnEnabledCategoryChanged(changedPlugin: PluginBase, type: PluginType) + + /** + * Fill LinearLayout with list of available plugins and checkboxes for enabling/disabling + * + * @param title Paragraph title or null if provided elsewhere + * @param description comment + * @param pluginType plugin category for example SYNC + * @param plugins list of plugins + * @param pluginViewHolders links to created UI elements (for calling `update` if configuration is changed) + * @param fragment + * @param activity either fragment or activity must be non null + * @param parent UI container to add views + */ + fun createViewsForPlugins( + @StringRes title: Int?, + @StringRes description: Int, + pluginType: PluginType, + plugins: List, + pluginViewHolders: ArrayList, + fragment: Fragment? = null, + activity: FragmentActivity? = null, + parent: LinearLayout + ) + + interface PluginViewHolderInterface { + + fun update() + } } \ No newline at end of file diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/sync/Tidepool.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/sync/Tidepool.kt new file mode 100644 index 0000000000..b49a390793 --- /dev/null +++ b/core/interfaces/src/main/java/info/nightscout/interfaces/sync/Tidepool.kt @@ -0,0 +1,3 @@ +package info.nightscout.interfaces.sync + +interface Tidepool : Sync \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 12b4051add..b04f2774ca 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -165,7 +165,7 @@ ISF Canceling of temporary basal failed Canceling of extended bolus failed - Upload status to NS + Upload status to NS or Tidepool Disabled/Suspended loop Insulin on Board (IOB) diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderFragment.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderFragment.kt index 34ea082c1c..f4d8e9b4b1 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderFragment.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderFragment.kt @@ -1,36 +1,24 @@ package info.nightscout.configuration.configBuilder -import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.CheckBox -import android.widget.ImageButton -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.RadioButton -import android.widget.TextView -import androidx.annotation.StringRes -import androidx.core.content.ContextCompat import dagger.android.support.DaggerFragment import info.nightscout.configuration.R import info.nightscout.configuration.configBuilder.events.EventConfigBuilderUpdateGui import info.nightscout.configuration.databinding.ConfigbuilderFragmentBinding import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.Config +import info.nightscout.interfaces.ConfigBuilder import info.nightscout.interfaces.plugin.ActivePlugin -import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.protection.ProtectionCheck import info.nightscout.interfaces.protection.ProtectionCheck.Protection.PREFERENCES import info.nightscout.interfaces.ui.UiInteraction import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.bus.RxBus -import info.nightscout.rx.events.EventRebuildTabs import info.nightscout.shared.extensions.toVisibility -import info.nightscout.shared.interfaces.ResourceHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject @@ -39,17 +27,15 @@ class ConfigBuilderFragment : DaggerFragment() { @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var rxBus: RxBus - @Inject lateinit var rh: ResourceHelper - @Inject lateinit var configBuilderPlugin: ConfigBuilderPlugin + @Inject lateinit var configBuilder: ConfigBuilder @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var protectionCheck: ProtectionCheck @Inject lateinit var config: Config - @Inject lateinit var ctx: Context @Inject lateinit var uiInteraction: UiInteraction private var disposable: CompositeDisposable = CompositeDisposable() - private val pluginViewHolders = ArrayList() + private val pluginViewHolders = ArrayList() private var inMenu = false private var queryingProtection = false private var _binding: ConfigbuilderFragmentBinding? = null @@ -98,119 +84,111 @@ class ConfigBuilderFragment : DaggerFragment() { @Synchronized private fun updateGUI() { binding.categories.removeAllViews() - createViewsForPlugins(R.string.configbuilder_profile, R.string.configbuilder_profile_description, PluginType.PROFILE, activePlugin.getSpecificPluginsVisibleInList(PluginType.PROFILE)) + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_profile, + description = R.string.configbuilder_profile_description, + pluginType = PluginType.PROFILE, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.PROFILE), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) if (config.APS || config.PUMPCONTROL || config.isEngineeringMode()) - createViewsForPlugins(info.nightscout.core.ui.R.string.configbuilder_insulin, R.string.configbuilder_insulin_description, PluginType.INSULIN, activePlugin.getSpecificPluginsVisibleInList(PluginType.INSULIN)) + configBuilder.createViewsForPlugins( + title = info.nightscout.core.ui.R.string.configbuilder_insulin, + description = R.string.configbuilder_insulin_description, + pluginType = PluginType.INSULIN, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.INSULIN), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) if (!config.NSCLIENT) { - createViewsForPlugins(R.string.configbuilder_bgsource, R.string.configbuilder_bgsource_description, PluginType.BGSOURCE, activePlugin.getSpecificPluginsVisibleInList(PluginType.BGSOURCE)) - createViewsForPlugins(R.string.configbuilder_smoothing, R.string.configbuilder_smoothing_description, PluginType.SMOOTHING, activePlugin.getSpecificPluginsVisibleInList(PluginType.SMOOTHING)) - createViewsForPlugins(R.string.configbuilder_pump, R.string.configbuilder_pump_description, PluginType.PUMP, activePlugin.getSpecificPluginsVisibleInList(PluginType.PUMP)) + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_bgsource, + description = R.string.configbuilder_bgsource_description, + pluginType = PluginType.BGSOURCE, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.BGSOURCE), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_smoothing, + description = R.string.configbuilder_smoothing_description, + pluginType = PluginType.SMOOTHING, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.SMOOTHING), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_pump, + description = R.string.configbuilder_pump_description, + pluginType = PluginType.PUMP, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.PUMP), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) } if (config.APS || config.PUMPCONTROL || config.isEngineeringMode()) - createViewsForPlugins(R.string.configbuilder_sensitivity, R.string.configbuilder_sensitivity_description, PluginType.SENSITIVITY, activePlugin.getSpecificPluginsVisibleInList(PluginType.SENSITIVITY)) + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_sensitivity, + description = R.string.configbuilder_sensitivity_description, + pluginType = PluginType.SENSITIVITY, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.SENSITIVITY), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) if (config.APS) { - createViewsForPlugins(R.string.configbuilder_aps, R.string.configbuilder_aps_description, PluginType.APS, activePlugin.getSpecificPluginsVisibleInList(PluginType.APS)) - createViewsForPlugins(R.string.configbuilder_loop, R.string.configbuilder_loop_description, PluginType.LOOP, activePlugin.getSpecificPluginsVisibleInList(PluginType.LOOP)) - createViewsForPlugins(info.nightscout.core.ui.R.string.constraints, R.string.configbuilder_constraints_description, PluginType.CONSTRAINTS, activePlugin.getSpecificPluginsVisibleInList(PluginType.CONSTRAINTS)) - } - createViewsForPlugins(R.string.configbuilder_sync, R.string.configbuilder_sync_description, PluginType.SYNC, activePlugin.getSpecificPluginsVisibleInList(PluginType.SYNC)) - createViewsForPlugins(R.string.configbuilder_general, R.string.configbuilder_general_description, PluginType.GENERAL, activePlugin.getSpecificPluginsVisibleInList(PluginType.GENERAL)) - } - - private fun createViewsForPlugins(@StringRes title: Int, @StringRes description: Int, pluginType: PluginType, plugins: List) { - if (plugins.isEmpty()) return - @Suppress("InflateParams") - val parent = layoutInflater.inflate(R.layout.configbuilder_single_category, null) as LinearLayout - (parent.findViewById(R.id.category_title) as TextView).text = rh.gs(title) - (parent.findViewById(R.id.category_description) as TextView).text = rh.gs(description) - val pluginContainer = parent.findViewById(R.id.category_plugins) - for (plugin in plugins) { - val pluginViewHolder = PluginViewHolder(this, pluginType, plugin) - pluginContainer.addView(pluginViewHolder.baseView) - pluginViewHolders.add(pluginViewHolder) - } - binding.categories.addView(parent) - } - - inner class PluginViewHolder internal constructor(private val fragment: ConfigBuilderFragment, - private val pluginType: PluginType, - private val plugin: PluginBase - ) { - - @Suppress("InflateParams") - val baseView: LinearLayout = fragment.layoutInflater.inflate(R.layout.configbuilder_single_plugin, null) as LinearLayout - private val enabledExclusive: RadioButton = baseView.findViewById(R.id.plugin_enabled_exclusive) - private val enabledInclusive: CheckBox = baseView.findViewById(R.id.plugin_enabled_inclusive) - private val pluginIcon: ImageView = baseView.findViewById(R.id.plugin_icon) - private val pluginIcon2: ImageView = baseView.findViewById(R.id.plugin_icon2) - private val pluginName: TextView = baseView.findViewById(R.id.plugin_name) - private val pluginDescription: TextView = baseView.findViewById(R.id.plugin_description) - private val pluginPreferences: ImageButton = baseView.findViewById(R.id.plugin_preferences) - private val pluginVisibility: CheckBox = baseView.findViewById(R.id.plugin_visibility) - - init { - - pluginVisibility.setOnClickListener { - plugin.setFragmentVisible(pluginType, pluginVisibility.isChecked) - configBuilderPlugin.storeSettings("CheckedCheckboxVisible") - rxBus.send(EventRebuildTabs()) - configBuilderPlugin.logPluginStatus() - } - - enabledExclusive.setOnClickListener { - configBuilderPlugin.switchAllowed(plugin, if (enabledExclusive.visibility == View.VISIBLE) enabledExclusive.isChecked else enabledInclusive.isChecked, fragment.activity, pluginType) - } - enabledInclusive.setOnClickListener { - configBuilderPlugin.switchAllowed(plugin, if (enabledExclusive.visibility == View.VISIBLE) enabledExclusive.isChecked else enabledInclusive.isChecked, fragment.activity, pluginType) - } - - pluginPreferences.setOnClickListener { - fragment.activity?.let { activity -> - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, { - val i = Intent(ctx, uiInteraction.preferencesActivity) - i.putExtra("id", plugin.preferencesId) - fragment.startActivity(i) - }, null) - } - } - update() - } - - fun update() { - enabledExclusive.visibility = areMultipleSelectionsAllowed(pluginType).not().toVisibility() - enabledInclusive.visibility = areMultipleSelectionsAllowed(pluginType).toVisibility() - enabledExclusive.isChecked = plugin.isEnabled(pluginType) - enabledInclusive.isChecked = plugin.isEnabled(pluginType) - enabledInclusive.isEnabled = !plugin.pluginDescription.alwaysEnabled - enabledExclusive.isEnabled = !plugin.pluginDescription.alwaysEnabled - if (plugin.menuIcon != -1) { - pluginIcon.visibility = View.VISIBLE - pluginIcon.setImageDrawable(context?.let { ContextCompat.getDrawable(it, plugin.menuIcon) }) - if (plugin.menuIcon2 != -1) { - pluginIcon2.visibility = View.VISIBLE - pluginIcon2.setImageDrawable(context?.let { ContextCompat.getDrawable(it, plugin.menuIcon2) }) - } else { - pluginIcon2.visibility = View.GONE - } - } else { - pluginIcon.visibility = View.GONE - } - pluginName.text = plugin.name - if (plugin.description == null) - pluginDescription.visibility = View.GONE - else { - pluginDescription.visibility = View.VISIBLE - pluginDescription.text = plugin.description - } - pluginPreferences.visibility = if (plugin.preferencesId == -1 || !plugin.isEnabled(pluginType)) View.INVISIBLE else View.VISIBLE - pluginVisibility.visibility = plugin.hasFragment().toVisibility() - pluginVisibility.isEnabled = !(plugin.pluginDescription.neverVisible || plugin.pluginDescription.alwaysVisible) && plugin.isEnabled(pluginType) - pluginVisibility.isChecked = plugin.isFragmentVisible() - } - - private fun areMultipleSelectionsAllowed(type: PluginType): Boolean { - return type == PluginType.GENERAL || type == PluginType.CONSTRAINTS || type == PluginType.LOOP || type == PluginType.SYNC + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_aps, + description = R.string.configbuilder_aps_description, + pluginType = PluginType.APS, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.APS), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_loop, + description = R.string.configbuilder_loop_description, + pluginType = PluginType.LOOP, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.LOOP), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) + configBuilder.createViewsForPlugins( + title = info.nightscout.core.ui.R.string.constraints, + description = R.string.configbuilder_constraints_description, + pluginType = PluginType.CONSTRAINTS, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.CONSTRAINTS), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) } + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_sync, + description = R.string.configbuilder_sync_description, + pluginType = PluginType.SYNC, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.SYNC), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) + configBuilder.createViewsForPlugins( + title = R.string.configbuilder_general, + description = R.string.configbuilder_general_description, + pluginType = PluginType.GENERAL, + plugins = activePlugin.getSpecificPluginsVisibleInList(PluginType.GENERAL), + pluginViewHolders = pluginViewHolders, + fragment = this, + parent = binding.categories + ) } private fun updateProtectedUi() { diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderPlugin.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderPlugin.kt index 5d73e8c498..cb6912c91b 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderPlugin.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/configBuilder/ConfigBuilderPlugin.kt @@ -1,5 +1,17 @@ package info.nightscout.configuration.configBuilder +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import android.widget.CheckBox +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import dagger.android.HasAndroidInjector import info.nightscout.configuration.R @@ -18,19 +30,23 @@ import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.profile.ProfileSource +import info.nightscout.interfaces.protection.ProtectionCheck import info.nightscout.interfaces.pump.Pump import info.nightscout.interfaces.pump.PumpSync import info.nightscout.interfaces.smoothing.Smoothing import info.nightscout.interfaces.source.BgSource import info.nightscout.interfaces.sync.NsClient +import info.nightscout.interfaces.ui.UiInteraction import info.nightscout.rx.bus.RxBus import info.nightscout.rx.events.EventAppInitialized import info.nightscout.rx.events.EventConfigBuilderChange import info.nightscout.rx.events.EventRebuildTabs import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.LTag +import info.nightscout.shared.extensions.toVisibility import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.sharedPreferences.SP +import java.security.InvalidParameterException import javax.inject.Inject import javax.inject.Singleton @@ -43,7 +59,9 @@ class ConfigBuilderPlugin @Inject constructor( private val rxBus: RxBus, private val activePlugin: ActivePlugin, private val uel: UserEntryLogger, - private val pumpSync: PumpSync + private val pumpSync: PumpSync, + private val protectionCheck: ProtectionCheck, + private val uiInteraction: UiInteraction ) : PluginBase( PluginDescription() .mainType(PluginType.GENERAL) @@ -222,4 +240,115 @@ class ConfigBuilderPlugin @Inject constructor( } } } + + override fun createViewsForPlugins( + @StringRes title: Int?, + @StringRes description: Int, + pluginType: PluginType, + plugins: List, + pluginViewHolders: ArrayList, + fragment: Fragment?, + activity: FragmentActivity?, + parent: LinearLayout + ) { + if (plugins.isEmpty()) return + val layoutInflater = fragment?.layoutInflater ?: activity?.layoutInflater ?: throw InvalidParameterException() + + @Suppress("InflateParams") + val holder = layoutInflater.inflate(R.layout.configbuilder_single_category, null) as LinearLayout + (holder.findViewById(R.id.category_title) as TextView).let { + if (title != null) it.text = rh.gs(title) + else it.visibility = View.GONE + } + (holder.findViewById(R.id.category_description) as TextView).text = rh.gs(description) + val pluginContainer = holder.findViewById(R.id.category_plugins) + val appActivity = fragment?.activity ?: activity ?: throw InvalidParameterException() + for (plugin in plugins) { + val pluginViewHolder = PluginViewHolder(layoutInflater, appActivity, pluginType, plugin) + pluginContainer.addView(pluginViewHolder.baseView) + pluginViewHolders.add(pluginViewHolder) + } + parent.addView(holder) + } + + inner class PluginViewHolder internal constructor( + layoutInflater: LayoutInflater, + private val activity: FragmentActivity, + private val pluginType: PluginType, + private val plugin: PluginBase + ) : ConfigBuilder.PluginViewHolderInterface { + + @Suppress("InflateParams") + val baseView: LinearLayout = layoutInflater.inflate(R.layout.configbuilder_single_plugin, null) as LinearLayout + private val enabledExclusive: RadioButton = baseView.findViewById(R.id.plugin_enabled_exclusive) + private val enabledInclusive: CheckBox = baseView.findViewById(R.id.plugin_enabled_inclusive) + private val pluginIcon: ImageView = baseView.findViewById(R.id.plugin_icon) + private val pluginIcon2: ImageView = baseView.findViewById(R.id.plugin_icon2) + private val pluginName: TextView = baseView.findViewById(R.id.plugin_name) + private val pluginDescription: TextView = baseView.findViewById(R.id.plugin_description) + private val pluginPreferences: ImageButton = baseView.findViewById(R.id.plugin_preferences) + private val pluginVisibility: CheckBox = baseView.findViewById(R.id.plugin_visibility) + + init { + + pluginVisibility.setOnClickListener { + plugin.setFragmentVisible(pluginType, pluginVisibility.isChecked) + storeSettings("CheckedCheckboxVisible") + rxBus.send(EventRebuildTabs()) + logPluginStatus() + } + + enabledExclusive.setOnClickListener { + switchAllowed(plugin, if (enabledExclusive.visibility == View.VISIBLE) enabledExclusive.isChecked else enabledInclusive.isChecked, activity, pluginType) + } + enabledInclusive.setOnClickListener { + switchAllowed(plugin, if (enabledExclusive.visibility == View.VISIBLE) enabledExclusive.isChecked else enabledInclusive.isChecked, activity, pluginType) + } + + pluginPreferences.setOnClickListener { + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, { + val i = Intent(activity, uiInteraction.preferencesActivity) + i.putExtra("id", plugin.preferencesId) + activity.startActivity(i) + }, null) + } + update() + } + + override fun update() { + enabledExclusive.visibility = areMultipleSelectionsAllowed(pluginType).not().toVisibility() + enabledInclusive.visibility = areMultipleSelectionsAllowed(pluginType).toVisibility() + enabledExclusive.isChecked = plugin.isEnabled(pluginType) + enabledInclusive.isChecked = plugin.isEnabled(pluginType) + enabledInclusive.isEnabled = !plugin.pluginDescription.alwaysEnabled + enabledExclusive.isEnabled = !plugin.pluginDescription.alwaysEnabled + if (plugin.menuIcon != -1) { + pluginIcon.visibility = View.VISIBLE + pluginIcon.setImageDrawable(ContextCompat.getDrawable(activity, plugin.menuIcon)) + if (plugin.menuIcon2 != -1) { + pluginIcon2.visibility = View.VISIBLE + pluginIcon2.setImageDrawable(ContextCompat.getDrawable(activity, plugin.menuIcon2)) + } else { + pluginIcon2.visibility = View.GONE + } + } else { + pluginIcon.visibility = View.GONE + } + pluginName.text = plugin.name + if (plugin.description == null) + pluginDescription.visibility = View.GONE + else { + pluginDescription.visibility = View.VISIBLE + pluginDescription.text = plugin.description + } + pluginPreferences.visibility = if (plugin.preferencesId == -1 || !plugin.isEnabled(pluginType)) View.INVISIBLE else View.VISIBLE + pluginVisibility.visibility = plugin.hasFragment().toVisibility() + pluginVisibility.isEnabled = !(plugin.pluginDescription.neverVisible || plugin.pluginDescription.alwaysVisible) && plugin.isEnabled(pluginType) + pluginVisibility.isChecked = plugin.isFragmentVisible() + } + + private fun areMultipleSelectionsAllowed(type: PluginType): Boolean { + return type == PluginType.GENERAL || type == PluginType.CONSTRAINTS || type == PluginType.LOOP || type == PluginType.SYNC + } + } } \ No newline at end of file diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt index 34ccaff3df..27465d04f4 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt @@ -71,7 +71,7 @@ class SWDefinition @Inject constructor( ) { lateinit var activity: AppCompatActivity - private val screens: MutableList = ArrayList() + private val screens: MutableList = ArrayList() fun getScreens(): List { if (screens.isEmpty()) { @@ -87,28 +87,25 @@ class SWDefinition @Inject constructor( return this } - private val screenSetupWizard get() = SWScreen(injector, R.string.nav_setupwizard) - .add( - SWInfoText(injector) - .label(R.string.welcometosetupwizard) - ) - private val screenEula get() = SWScreen(injector, R.string.end_user_license_agreement) - .skippable(false) - .add( - SWInfoText(injector) - .label(R.string.end_user_license_agreement_text) - ) - .add(SWBreak(injector)) - .add( - SWButton(injector) - .text(R.string.end_user_license_agreement_i_understand) - .visibility { !sp.getBoolean(R.string.key_i_understand, false) } - .action { - sp.putBoolean(R.string.key_i_understand, true) - rxBus.send(EventSWUpdate(false)) - }) - .visibility { !sp.getBoolean(R.string.key_i_understand, false) } - .validator { sp.getBoolean(R.string.key_i_understand, false) } + private val screenSetupWizard + get() = SWScreen(injector, R.string.nav_setupwizard) + .add(SWInfoText(injector).label(R.string.welcometosetupwizard)) + + private val screenEula + get() = SWScreen(injector, R.string.end_user_license_agreement) + .skippable(false) + .add(SWInfoText(injector).label(R.string.end_user_license_agreement_text)) + .add(SWBreak(injector)) + .add( + SWButton(injector) + .text(R.string.end_user_license_agreement_i_understand) + .visibility { !sp.getBoolean(R.string.key_i_understand, false) } + .action { + sp.putBoolean(R.string.key_i_understand, true) + rxBus.send(EventSWUpdate(false)) + }) + .visibility { !sp.getBoolean(R.string.key_i_understand, false) } + .validator { sp.getBoolean(R.string.key_i_understand, false) } private val screenUnits get() = SWScreen(injector, R.string.units) @@ -143,185 +140,132 @@ class SWDefinition @Inject constructor( private val screenPermissionWindow get() = SWScreen(injector, R.string.permission) .skippable(false) + .add(SWInfoText(injector).label(rh.gs(R.string.need_system_window_permission))) + .add(SWBreak(injector)) + .add(SWButton(injector) + .text(R.string.askforpermission) + .visibility { !Settings.canDrawOverlays(activity) } + .action { activity.startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.packageName))) }) + .visibility { !Settings.canDrawOverlays(activity) } + .validator { Settings.canDrawOverlays(activity) } + + private val screenPermissionBattery + get() = SWScreen(injector, R.string.permission) + .skippable(false) + .add(SWInfoText(injector).label(rh.gs(R.string.need_whitelisting, rh.gs(config.appName)))) + .add(SWBreak(injector)) + .add(SWButton(injector) + .text(R.string.askforpermission) + .visibility { androidPermission.permissionNotGranted(context, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) } + .action { androidPermission.askForPermission(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) + .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) } + .validator { !androidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) } + + private val screenPermissionBt + get() = SWScreen(injector, R.string.permission) + .skippable(false) + .add(SWInfoText(injector).label(rh.gs(R.string.need_location_permission))) + .add(SWBreak(injector)) + .add(SWButton(injector) + .text(R.string.askforpermission) + .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) } + .action { androidPermission.askForPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) + .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) } + .validator { !androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) } + + private val screenPermissionStore + get() = SWScreen(injector, R.string.permission) + .skippable(false) + .add(SWInfoText(injector).label(rh.gs(R.string.need_storage_permission))) + .add(SWBreak(injector)) + .add(SWButton(injector) + .text(R.string.askforpermission) + .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } + .action { androidPermission.askForPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) + .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } + .validator { !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } + + private val screenImport + get() = SWScreen(injector, R.string.import_setting) + .add(SWInfoText(injector).label(R.string.storedsettingsfound)) + .add(SWBreak(injector)) + .add(SWButton(injector).text(R.string.import_setting).action { importExportPrefs.importSharedPreferences(activity) }) + .visibility { importExportPrefs.prefsFileExists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } + + private val screenNsClient + get() = SWScreen(injector, R.string.configbuilder_sync) + .skippable(true) + .add(SWPlugin(injector, this).option(PluginType.SYNC, R.string.configbuilder_sync_description)) + .add(SWBreak(injector)) + .add(SWInfoText(injector).label(R.string.syncinfotext)) + .add(SWBreak(injector)) + .add(SWEventListener(injector, EventSWSyncStatus::class.java).label(R.string.status).initialStatus(activePlugin.activeNsClient?.status ?: "")) + .validator { activePlugin.activeNsClient?.connected == true && activePlugin.activeNsClient?.hasWritePermission == true } + + private val screenPatientName + get() = SWScreen(injector, R.string.patient_name) + .skippable(true) + .add(SWInfoText(injector).label(R.string.patient_name_summary)) + .add(SWEditString(injector).validator(SWTextValidator(String::isNotEmpty)).preferenceId(info.nightscout.core.utils.R.string.key_patient_name)) + + private val privacy + get() = SWScreen(injector, R.string.privacy_settings) + .skippable(true) + .add(SWInfoText(injector).label(R.string.privacy_summary)) + .add(SWPreference(injector, this).option(R.xml.pref_datachoices)) + + private val screenMasterPassword + get() = SWScreen(injector, info.nightscout.core.ui.R.string.master_password) + .skippable(false) + .add(SWInfoText(injector).label(info.nightscout.core.ui.R.string.master_password)) + .add(SWEditEncryptedPassword(injector, cryptoUtil).preferenceId(info.nightscout.core.utils.R.string.key_master_password)) + .add(SWBreak(injector)) + .add(SWInfoText(injector).label(R.string.master_password_summary)) + .validator { !cryptoUtil.checkPassword("", sp.getString(info.nightscout.core.utils.R.string.key_master_password, "")) } + + private val screenAge + get() = SWScreen(injector, info.nightscout.core.ui.R.string.patient_type) + .skippable(false) + .add(SWBreak(injector)) .add( - SWInfoText(injector) - .label(rh.gs(R.string.need_system_window_permission)) - ) - .add(SWBreak(injector)) - .add(SWButton(injector) - .text(R.string.askforpermission) - .visibility { !Settings.canDrawOverlays(activity) } - .action { activity.startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.packageName))) }) - .visibility { !Settings.canDrawOverlays(activity) } - .validator { Settings.canDrawOverlays(activity) } + SWRadioButton(injector) + .option(info.nightscout.core.ui.R.array.ageArray, info.nightscout.core.utils.R.array.ageValues) + .preferenceId(info.nightscout.core.utils.R.string.key_age) + .label(info.nightscout.core.ui.R.string.patient_type) + .comment(info.nightscout.core.ui.R.string.patient_age_summary) + ) + .add(SWBreak(injector)) + .add( + SWEditNumber(injector, 3.0, 0.1, 25.0) + .preferenceId(info.nightscout.core.utils.R.string.key_treatmentssafety_maxbolus) + .updateDelay(5) + .label(info.nightscout.core.ui.R.string.max_bolus_title) + .comment(R.string.common_values) + ) + .add( + SWEditIntNumber(injector, 48, 1, 100) + .preferenceId(info.nightscout.core.utils.R.string.key_treatmentssafety_maxcarbs) + .updateDelay(5) + .label(info.nightscout.core.ui.R.string.max_carbs_title) + .comment(R.string.common_values) + ) + .validator { + sp.contains(info.nightscout.core.utils.R.string.key_age) + && sp.getDouble(info.nightscout.core.utils.R.string.key_treatmentssafety_maxbolus, 0.0) > 0 + && sp.getInt(info.nightscout.core.utils.R.string.key_treatmentssafety_maxcarbs, 0) > 0 + } - private val screenPermissionBattery get() = SWScreen(injector, R.string.permission) - .skippable(false) - .add( - SWInfoText(injector) - .label(rh.gs(R.string.need_whitelisting, rh.gs(config.appName))) - ) - .add(SWBreak(injector)) - .add(SWButton(injector) - .text(R.string.askforpermission) - .visibility { androidPermission.permissionNotGranted(context, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) } - .action { androidPermission.askForPermission(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) - .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) } - .validator { !androidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) } - - private val screenPermissionBt get() = SWScreen(injector, R.string.permission) - .skippable(false) - .add( - SWInfoText(injector) - .label(rh.gs(R.string.need_location_permission)) - ) - .add(SWBreak(injector)) - .add(SWButton(injector) - .text(R.string.askforpermission) - .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) } - .action { androidPermission.askForPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) - .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) } - .validator { !androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) } - - private val screenPermissionStore get() = SWScreen(injector, R.string.permission) - .skippable(false) - .add( - SWInfoText(injector) - .label(rh.gs(R.string.need_storage_permission)) - ) - .add(SWBreak(injector)) - .add(SWButton(injector) - .text(R.string.askforpermission) - .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } - .action { androidPermission.askForPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) - .visibility { androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } - .validator { !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } - - private val screenImport get() = SWScreen(injector, R.string.import_setting) - .add( - SWInfoText(injector) - .label(R.string.storedsettingsfound) - ) - .add(SWBreak(injector)) - .add(SWButton(injector) - .text(R.string.import_setting) - .action { importExportPrefs.importSharedPreferences(activity) }) - .visibility { importExportPrefs.prefsFileExists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) } - - private val screenNsClient get() = SWScreen(injector, R.string.configbuilder_sync) - .skippable(true) - .add( - SWPlugin(injector, this) - .option(PluginType.SYNC, R.string.configbuilder_sync_description) - .makeVisible(false) - .label(info.nightscout.core.ui.R.string.configbuilder_insulin) - ) - .add(SWBreak(injector)) - .add( - SWInfoText(injector) - .label(R.string.syncinfotext) - ) - .add(SWBreak(injector)) - .add( - SWEventListener(injector, EventSWSyncStatus::class.java) - .label(R.string.status) - .initialStatus(activePlugin.activeNsClient?.status ?: "") - ) - .validator { activePlugin.activeNsClient?.connected == true && activePlugin.activeNsClient?.hasWritePermission == true } - - private val screenPatientName get() = SWScreen(injector, R.string.patient_name) - .skippable(true) - .add( - SWInfoText(injector) - .label(R.string.patient_name_summary) - ) - .add( - SWEditString(injector) - .validator(SWTextValidator(String::isNotEmpty)) - .preferenceId(info.nightscout.core.utils.R.string.key_patient_name) - ) - - private val privacy get() = SWScreen(injector, R.string.privacy_settings) - .skippable(true) - .add( - SWInfoText(injector) - .label(R.string.privacy_summary) - ) - .add( - SWPreference(injector, this) - .option(R.xml.pref_datachoices) - ) - - private val screenMasterPassword get() = SWScreen(injector, info.nightscout.core.ui.R.string.master_password) - .skippable(false) - .add( - SWInfoText(injector) - .label(info.nightscout.core.ui.R.string.master_password) - ) - .add( - SWEditEncryptedPassword(injector, cryptoUtil) - .preferenceId(info.nightscout.core.utils.R.string.key_master_password) - ) - .add(SWBreak(injector)) - .add( - SWInfoText(injector) - .label(R.string.master_password_summary) - ) - .validator { !cryptoUtil.checkPassword("", sp.getString(info.nightscout.core.utils.R.string.key_master_password, "")) } - - private val screenAge get() = SWScreen(injector, info.nightscout.core.ui.R.string.patient_type) - .skippable(false) - .add(SWBreak(injector)) - .add( - SWRadioButton(injector) - .option(info.nightscout.core.ui.R.array.ageArray, info.nightscout.core.utils.R.array.ageValues) - .preferenceId(info.nightscout.core.utils.R.string.key_age) - .label(info.nightscout.core.ui.R.string.patient_type) - .comment(info.nightscout.core.ui.R.string.patient_age_summary) - ) - .add(SWBreak(injector)) - .add( - SWEditNumber(injector, 3.0, 0.1, 25.0) - .preferenceId(info.nightscout.core.utils.R.string.key_treatmentssafety_maxbolus) - .updateDelay(5) - .label(info.nightscout.core.ui.R.string.max_bolus_title) - .comment(R.string.common_values) - ) - .add( - SWEditIntNumber(injector, 48, 1, 100) - .preferenceId(info.nightscout.core.utils.R.string.key_treatmentssafety_maxcarbs) - .updateDelay(5) - .label(info.nightscout.core.ui.R.string.max_carbs_title) - .comment(R.string.common_values) - ) - .validator { - sp.contains(info.nightscout.core.utils.R.string.key_age) - && sp.getDouble(info.nightscout.core.utils.R.string.key_treatmentssafety_maxbolus, 0.0) > 0 - && sp.getInt(info.nightscout.core.utils.R.string.key_treatmentssafety_maxcarbs, 0) > 0 - } - - private val screenInsulin get() = SWScreen(injector, info.nightscout.core.ui.R.string.configbuilder_insulin) - .skippable(false) - .add( - SWPlugin(injector, this) - .option(PluginType.INSULIN, R.string.configbuilder_insulin_description) - .makeVisible(false) - .label(info.nightscout.core.ui.R.string.configbuilder_insulin) - ) - .add(SWBreak(injector)) - .add( - SWInfoText(injector) - .label(R.string.diawarning) - ) + private val screenInsulin + get() = SWScreen(injector, info.nightscout.core.ui.R.string.configbuilder_insulin) + .skippable(false) + .add(SWPlugin(injector, this).option(PluginType.INSULIN, R.string.configbuilder_insulin_description)) + .add(SWBreak(injector)) + .add(SWInfoText(injector).label(R.string.diawarning)) private val screenBgSource get() = SWScreen(injector, R.string.configbuilder_bgsource) .skippable(false) - .add( - SWPlugin(injector, this) - .option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description) - .label(R.string.configbuilder_bgsource) - ) + .add(SWPlugin(injector, this).option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description)) .add(SWBreak(injector)) private val screenLocalProfile @@ -346,50 +290,42 @@ class SWDefinition @Inject constructor( private val screenProfileSwitch get() = SWScreen(injector, info.nightscout.core.ui.R.string.careportal_profileswitch) - .skippable(false) - .add( - SWInfoText(injector) - .label(info.nightscout.core.ui.R.string.profileswitch_ismissing) - ) - .add(SWButton(injector) - .text(R.string.doprofileswitch) - .action { uiInteraction.runProfileSwitchDialog(activity.supportFragmentManager) }) - .validator { profileFunction.getRequestedProfile() != null } - .visibility { profileFunction.getRequestedProfile() == null } + .skippable(false) + .add(SWInfoText(injector).label(info.nightscout.core.ui.R.string.profileswitch_ismissing)) + .add(SWButton(injector) + .text(R.string.doprofileswitch) + .action { uiInteraction.runProfileSwitchDialog(activity.supportFragmentManager) }) + .validator { profileFunction.getRequestedProfile() != null } + .visibility { profileFunction.getRequestedProfile() == null } - private val screenPump get() = SWScreen(injector, R.string.configbuilder_pump) - .skippable(false) - .add( - SWPlugin(injector, this) - .option(PluginType.PUMP, R.string.configbuilder_pump_description) - .label(R.string.configbuilder_pump) - ) - .add(SWBreak(injector)) - .add(SWInfoText(injector) - .label(R.string.setupwizard_pump_pump_not_initialized) - .visibility { !isPumpInitialized() }) - .add( // Omnipod Eros only - SWInfoText(injector) - .label(R.string.setupwizard_pump_waiting_for_riley_link_connection) - .visibility { - val activePump = activePlugin.activePump - activePump is OmnipodEros && !activePump.isRileyLinkReady() - }) - .add( // Omnipod Eros only - SWEventListener(injector, EventSWRLStatus::class.java) - .label(R.string.setupwizard_pump_riley_link_status) - .visibility { activePlugin.activePump is OmnipodEros }) - .add(SWButton(injector) - .text(R.string.readstatus) - .action { commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.clicked_connect_to_pump), null) } - .visibility { - // Hide for Omnipod, because as we don't require a Pod to be paired in the setup wizard, - // Getting the status might not be possible - activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash - }) - .add(SWEventListener(injector, EventPumpStatusChanged::class.java) - .visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash }) - .validator { isPumpInitialized() } + private val screenPump + get() = SWScreen(injector, R.string.configbuilder_pump) + .skippable(false) + .add(SWPlugin(injector, this).option(PluginType.PUMP, R.string.configbuilder_pump_description)) + .add(SWBreak(injector)) + .add(SWInfoText(injector).label(R.string.setupwizard_pump_pump_not_initialized).visibility { !isPumpInitialized() }) + .add( // Omnipod Eros only + SWInfoText(injector) + .label(R.string.setupwizard_pump_waiting_for_riley_link_connection) + .visibility { + val activePump = activePlugin.activePump + activePump is OmnipodEros && !activePump.isRileyLinkReady() + }) + .add( // Omnipod Eros only + SWEventListener(injector, EventSWRLStatus::class.java) + .label(R.string.setupwizard_pump_riley_link_status) + .visibility { activePlugin.activePump is OmnipodEros }) + .add(SWButton(injector) + .text(R.string.readstatus) + .action { commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.clicked_connect_to_pump), null) } + .visibility { + // Hide for Omnipod, because as we don't require a Pod to be paired in the setup wizard, + // Getting the status might not be possible + activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash + }) + .add(SWEventListener(injector, EventPumpStatusChanged::class.java) + .visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash }) + .validator { isPumpInitialized() } private fun isPumpInitialized(): Boolean { val activePump = activePlugin.activePump @@ -402,83 +338,67 @@ class SWDefinition @Inject constructor( || activePump is OmnipodDash } - private val screenAps get() = SWScreen(injector, R.string.configbuilder_aps) - .skippable(false) - .add( - SWInfoText(injector) - .label(R.string.setupwizard_aps_description) - ) - .add(SWBreak(injector)) - .add( - SWPlugin(injector, this) - .option(PluginType.APS, R.string.configbuilder_aps_description) - .label(R.string.configbuilder_aps) - ) - .add(SWBreak(injector)) - .add( - SWHtmlLink(injector) - .label("https://wiki.aaps.app") - ) - .add(SWBreak(injector)) + private val screenAps + get() = SWScreen(injector, R.string.configbuilder_aps) + .skippable(false) + .add(SWInfoText(injector).label(R.string.setupwizard_aps_description)) + .add(SWBreak(injector)) + .add(SWPlugin(injector, this).option(PluginType.APS, R.string.configbuilder_aps_description)) + .add(SWBreak(injector)) + .add(SWHtmlLink(injector).label("https://wiki.aaps.app")) + .add(SWBreak(injector)) - private val screenApsMode get() = SWScreen(injector, R.string.apsmode_title) - .skippable(false) - .add( - SWRadioButton(injector) - .option(info.nightscout.core.ui.R.array.aps_modeArray, info.nightscout.core.ui.R.array.aps_modeValues) - .preferenceId(info.nightscout.core.utils.R.string.key_aps_mode).label(R.string.apsmode_title) - .comment(R.string.setupwizard_preferred_aps_mode) - ) - .validator { sp.contains(info.nightscout.core.utils.R.string.key_aps_mode) } + private val screenApsMode + get() = SWScreen(injector, R.string.apsmode_title) + .skippable(false) + .add( + SWRadioButton(injector) + .option(info.nightscout.core.ui.R.array.aps_modeArray, info.nightscout.core.ui.R.array.aps_modeValues) + .preferenceId(info.nightscout.core.utils.R.string.key_aps_mode).label(R.string.apsmode_title) + .comment(R.string.setupwizard_preferred_aps_mode) + ) + .validator { sp.contains(info.nightscout.core.utils.R.string.key_aps_mode) } - private val screenLoop get() = SWScreen(injector, R.string.configbuilder_loop) - .skippable(false) - .add( - SWInfoText(injector) - .label(R.string.setupwizard_loop_description) - ) - .add(SWBreak(injector)) - .add(SWButton(injector) - .text(info.nightscout.core.ui.R.string.enableloop) - .action { - configBuilder.performPluginSwitch(loop as PluginBase, true, PluginType.LOOP) - rxBus.send(EventSWUpdate(true)) - } - .visibility { !loop.isEnabled() }) - .validator { loop.isEnabled() } - .visibility { !loop.isEnabled() && config.APS } + private val screenLoop + get() = SWScreen(injector, R.string.configbuilder_loop) + .skippable(false) + .add(SWInfoText(injector).label(R.string.setupwizard_loop_description)) + .add(SWBreak(injector)) + .add(SWButton(injector) + .text(info.nightscout.core.ui.R.string.enableloop) + .action { + configBuilder.performPluginSwitch(loop as PluginBase, true, PluginType.LOOP) + rxBus.send(EventSWUpdate(true)) + } + .visibility { !loop.isEnabled() }) + .validator { loop.isEnabled() } + .visibility { !loop.isEnabled() && config.APS } - private val screenSensitivity get() = SWScreen(injector, R.string.configbuilder_sensitivity) - .skippable(false) - .add( - SWInfoText(injector) - .label(R.string.setupwizard_sensitivity_description) - ) - .add( - SWHtmlLink(injector) - .label(R.string.setupwizard_sensitivity_url) - ) - .add(SWBreak(injector)) - .add( - SWPlugin(injector, this) - .option(PluginType.SENSITIVITY, R.string.configbuilder_sensitivity_description) - .label(R.string.configbuilder_sensitivity) - ) + private val screenSensitivity + get() = SWScreen(injector, R.string.configbuilder_sensitivity) + .skippable(false) + .add(SWInfoText(injector).label(R.string.setupwizard_sensitivity_description)) + .add(SWHtmlLink(injector).label(R.string.setupwizard_sensitivity_url)) + .add(SWBreak(injector)) + .add(SWPlugin(injector, this).option(PluginType.SENSITIVITY, R.string.configbuilder_sensitivity_description)) - private val getScreenObjectives get() = SWScreen(injector, info.nightscout.core.ui.R.string.objectives) - .skippable(false) - .add( - SWInfoText(injector) - .label(R.string.startobjective) - ) - .add(SWBreak(injector)) - .add( - SWFragment(injector, this) - .add(activity.supportFragmentManager.fragmentFactory.instantiate(ClassLoader.getSystemClassLoader(), (activePlugin.activeObjectives as PluginBase).pluginDescription.fragmentClass!!)) + private val getScreenObjectives + get() = SWScreen(injector, info.nightscout.core.ui.R.string.objectives) + .skippable(false) + .add(SWInfoText(injector).label(R.string.startobjective)) + .add(SWBreak(injector)) + .add( + SWFragment(injector, this) + .add( + activity.supportFragmentManager.fragmentFactory.instantiate( + ClassLoader.getSystemClassLoader(), + (activePlugin.activeObjectives as PluginBase).pluginDescription.fragmentClass!! + ) + ) //.add(ObjectivesFragment()) - ) - .validator { activePlugin.activeObjectives?.isStarted(Objectives.FIRST_OBJECTIVE) ?: false} - .visibility { config.APS && !(activePlugin.activeObjectives?.isStarted(Objectives.FIRST_OBJECTIVE) ?: false) } + ) + .validator { activePlugin.activeObjectives?.isStarted(Objectives.FIRST_OBJECTIVE) ?: false } + .visibility { config.APS && !(activePlugin.activeObjectives?.isStarted(Objectives.FIRST_OBJECTIVE) ?: false) } private fun swDefinitionFull() = // List all the screens here add(screenSetupWizard) diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWPlugin.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWPlugin.kt index 10f76a2223..f1a6cfdf33 100644 --- a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWPlugin.kt +++ b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/elements/SWPlugin.kt @@ -1,22 +1,19 @@ package info.nightscout.configuration.setupwizard.elements -import android.os.Bundle -import android.view.View -import android.view.ViewGroup import android.widget.LinearLayout -import android.widget.RadioButton -import android.widget.RadioGroup -import android.widget.TextView -import androidx.fragment.app.Fragment +import androidx.annotation.StringRes import dagger.android.HasAndroidInjector +import info.nightscout.configuration.configBuilder.events.EventConfigBuilderUpdateGui import info.nightscout.configuration.setupwizard.SWDefinition +import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.interfaces.ConfigBuilder import info.nightscout.interfaces.plugin.ActivePlugin -import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.ui.UiInteraction -import info.nightscout.rx.events.EventConfigBuilderChange -import info.nightscout.rx.events.EventSWUpdate +import info.nightscout.rx.AapsSchedulers +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import java.security.InvalidParameterException import javax.inject.Inject class SWPlugin(injector: HasAndroidInjector, private val definition: SWDefinition) : SWItem(injector, Type.PLUGIN) { @@ -24,89 +21,39 @@ class SWPlugin(injector: HasAndroidInjector, private val definition: SWDefinitio @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var configBuilder: ConfigBuilder @Inject lateinit var uiInteraction: UiInteraction + @Inject lateinit var aapsSchedulers: AapsSchedulers + @Inject lateinit var fabricPrivacy: FabricPrivacy + private val disposable = CompositeDisposable() + private val pluginViewHolders = ArrayList() private var pType: PluginType? = null - private var radioGroup: RadioGroup? = null - private var pluginDescription = 0 - private var makeVisible = true + @StringRes private var pluginDescription = 0 - private var fragment: Fragment? = null + // TODO: Adrian how to clear disposable in this case? + init { + disposable += rxBus + .toObservable(EventConfigBuilderUpdateGui::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update() }, fabricPrivacy::logException) + } - fun option(pType: PluginType, pluginDescription: Int): SWPlugin { + fun option(pType: PluginType, @StringRes pluginDescription: Int): SWPlugin { this.pType = pType this.pluginDescription = pluginDescription return this } - fun makeVisible(makeVisible: Boolean): SWPlugin { - this.makeVisible = makeVisible - return this - } - override fun generateDialog(layout: LinearLayout) { - var selectedPlugin: PluginBase? = null - val context = layout.context - radioGroup = RadioGroup(context) - radioGroup?.clearCheck() - val pluginsInCategory = activePlugin.getSpecificPluginsList(pType!!) - radioGroup?.orientation = LinearLayout.VERTICAL - radioGroup?.visibility = View.VISIBLE - val pDesc = TextView(context) - pDesc.setText(pluginDescription) - var params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - params.setMargins(0, 0, 0, 40) - pDesc.layoutParams = params - layout.addView(pDesc) - for (i in pluginsInCategory.indices) { - val rdBtn = RadioButton(context) - val p = pluginsInCategory[i] - rdBtn.id = View.generateViewId() - rdBtn.text = p.name - if (p.isEnabled()) { - rdBtn.isChecked = true - selectedPlugin = p - } - rdBtn.tag = p - radioGroup?.addView(rdBtn) - params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - params.setMargins(80, 0, 0, 0) - val desc = TextView(context) - desc.text = p.description - desc.layoutParams = params - radioGroup?.addView(desc) - } - radioGroup?.setOnCheckedChangeListener { group: RadioGroup, checkedId: Int -> - val rb = group.findViewById(checkedId) - val plugin = rb.tag as PluginBase - plugin.setPluginEnabled(pType!!, rb.isChecked) - plugin.setFragmentVisible(pType!!, rb.isChecked && makeVisible) - configBuilder.processOnEnabledCategoryChanged(plugin, pType!!) - configBuilder.storeSettings("SetupWizard") - rxBus.send(EventConfigBuilderChange()) - rxBus.send(EventSWUpdate(false)) - addConfiguration(layout, plugin) - } - layout.addView(radioGroup) - selectedPlugin?.let { addConfiguration(layout, it) } + val pType = this.pType ?: throw InvalidParameterException() + configBuilder.createViewsForPlugins( + title = null, + description = pluginDescription, + pluginType = pType, + plugins = activePlugin.getSpecificPluginsVisibleInList(pType), + pluginViewHolders = pluginViewHolders, + activity = definition.activity, + parent = layout + ) super.generateDialog(layout) } - - private fun addConfiguration(layout: LinearLayout, plugin: PluginBase) { - if (plugin.preferencesId != -1) { - fragment = Class.forName(uiInteraction.myPreferenceFragment.name).newInstance() as Fragment //MyPreferenceFragment() - fragment?.let { - it.arguments = Bundle().also { it.putInt("id", plugin.preferencesId) } - definition.activity.supportFragmentManager.beginTransaction().run { - replace(layout.id, it) - commit() - } - } - } else { - definition.activity.supportFragmentManager.beginTransaction().run { - fragment?.let { remove(it) } - fragment = null - commit() - } - } - } } \ No newline at end of file diff --git a/plugins/configuration/src/main/res/values/strings.xml b/plugins/configuration/src/main/res/values/strings.xml index 11d79252f0..1cec48dfba 100644 --- a/plugins/configuration/src/main/res/values/strings.xml +++ b/plugins/configuration/src/main/res/values/strings.xml @@ -28,7 +28,7 @@ Master password is used for backup encryption and to override security in application. Remember it or store on a safe place. Current master password Use values of your largest food you usually eat\n - Synchronize data to the cloud. You can skip this part now but you will not be able to pass objectives until you set it up. + Synchronize data to the cloud. You can skip this part now but you will not be able to pass objectives until you set up Nightscout or Tidepool client. Status: Patient name Please provide patient name or nickname to differentiate among multiple setups diff --git a/plugins/constraints/src/main/java/info/nightscout/plugins/constraints/objectives/objectives/Objective0.kt b/plugins/constraints/src/main/java/info/nightscout/plugins/constraints/objectives/objectives/Objective0.kt index dba33599fb..30f8842359 100644 --- a/plugins/constraints/src/main/java/info/nightscout/plugins/constraints/objectives/objectives/Objective0.kt +++ b/plugins/constraints/src/main/java/info/nightscout/plugins/constraints/objectives/objectives/Objective0.kt @@ -8,6 +8,7 @@ import info.nightscout.interfaces.iob.IobCobCalculator import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.pump.VirtualPump +import info.nightscout.interfaces.sync.Tidepool import info.nightscout.plugins.constraints.R import javax.inject.Inject @@ -18,21 +19,22 @@ class Objective0(injector: HasAndroidInjector) : Objective(injector, "config", R @Inject lateinit var persistenceLayer: PersistenceLayer @Inject lateinit var loop: Loop @Inject lateinit var iobCobCalculator: IobCobCalculator + val tidepoolPlugin get() = activePlugin.getSpecificPluginsListByInterface(Tidepool::class.java).firstOrNull() as Tidepool? init { tasks.add(object : Task(this, R.string.objectives_bgavailableinns) { override fun isCompleted(): Boolean { - return sp.getBoolean(info.nightscout.core.utils.R.string.key_objectives_bg_is_available_in_ns, false) + return sp.getBoolean(info.nightscout.core.utils.R.string.key_objectives_bg_is_available_in_ns, false) || tidepoolPlugin?.hasWritePermission == true } }) tasks.add(object : Task(this, R.string.synchaswritepermission) { override fun isCompleted(): Boolean { - return activePlugin.firstActiveSync?.hasWritePermission == true + return activePlugin.firstActiveSync?.hasWritePermission == true || tidepoolPlugin?.hasWritePermission == true } }) tasks.add(object : Task(this, info.nightscout.core.ui.R.string.virtualpump_uploadstatus_title) { override fun isCompleted(): Boolean { - return sp.getBoolean(info.nightscout.core.utils.R.string.key_virtual_pump_upload_status, false) + return sp.getBoolean(info.nightscout.core.utils.R.string.key_virtual_pump_upload_status, false) || tidepoolPlugin?.hasWritePermission == true } override fun shouldBeIgnored(): Boolean { @@ -42,7 +44,7 @@ class Objective0(injector: HasAndroidInjector) : Objective(injector, "config", R tasks.add( object : Task(this, R.string.objectives_pumpstatusavailableinns) { override fun isCompleted(): Boolean { - return sp.getBoolean(info.nightscout.core.utils.R.string.key_objectives_pump_status_is_available_in_ns, false) + return sp.getBoolean(info.nightscout.core.utils.R.string.key_objectives_pump_status_is_available_in_ns, false) || tidepoolPlugin?.hasWritePermission == true } }.learned(Learned(R.string.objectives_0_learned)) ) diff --git a/plugins/constraints/src/main/res/values/objectives.xml b/plugins/constraints/src/main/res/values/objectives.xml index 875019ae1a..39343a8b62 100644 --- a/plugins/constraints/src/main/res/values/objectives.xml +++ b/plugins/constraints/src/main/res/values/objectives.xml @@ -31,8 +31,8 @@ Enabling automation Read the docs on how automation works. Set up your first simple rules. Instead of action let AAPS display only notification. When you are sure automation is triggered at the right time replace notification by real action. (https://wiki.aaps.app/en/latest/Usage/Automation.html) Automation can be a good servant but a bad master. Don\'t overuse it. Do not try to replace underlying algorithm. Test the rule with message only before use. It depends on order. - BG available in NS - Pump status available in NS + BG available in NS or Tidepool + Pump status available in NS or Tidepool Manual enacts Accomplished: %1$s Learn how to control AAPS diff --git a/plugins/source/src/main/java/info/nightscout/source/RandomBgPlugin.kt b/plugins/source/src/main/java/info/nightscout/source/RandomBgPlugin.kt index 88e20a0972..cfee2bf8d2 100644 --- a/plugins/source/src/main/java/info/nightscout/source/RandomBgPlugin.kt +++ b/plugins/source/src/main/java/info/nightscout/source/RandomBgPlugin.kt @@ -92,7 +92,7 @@ class RandomBgPlugin @Inject constructor( } override fun specialEnableCondition(): Boolean { - return isRunningTest() || config.isUnfinishedMode() || virtualPump.isEnabled() && config.isEngineeringMode() + return isRunningTest() || virtualPump.isEnabled() && config.isEngineeringMode() } private fun handleNewData() { diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/dataBroadcaster/DataBroadcastPlugin.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/dataBroadcaster/DataBroadcastPlugin.kt index ba6cf0b364..8cc229e550 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/dataBroadcaster/DataBroadcastPlugin.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/dataBroadcaster/DataBroadcastPlugin.kt @@ -62,9 +62,7 @@ class DataBroadcastPlugin @Inject constructor( PluginDescription() .mainType(PluginType.SYNC) .pluginName(R.string.data_broadcaster) - .alwaysEnabled(true) - .neverVisible(true) - .showInList(false), + .description(R.string.data_broadcaster_description), aapsLogger, rh, injector ) { diff --git a/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolPlugin.kt b/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolPlugin.kt index 521d21e66f..0bcbf14233 100644 --- a/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolPlugin.kt +++ b/plugins/sync/src/main/java/info/nightscout/plugins/sync/tidepool/TidepoolPlugin.kt @@ -11,6 +11,7 @@ import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.sync.Sync +import info.nightscout.interfaces.sync.Tidepool import info.nightscout.interfaces.ui.UiInteraction import info.nightscout.interfaces.utils.HtmlHelper import info.nightscout.plugins.sync.R @@ -28,6 +29,7 @@ import info.nightscout.rx.bus.RxBus import info.nightscout.rx.events.EventNSClientNewLog import info.nightscout.rx.events.EventNewBG import info.nightscout.rx.events.EventPreferenceChange +import info.nightscout.rx.events.EventSWSyncStatus import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.LTag import info.nightscout.shared.interfaces.ResourceHelper @@ -53,7 +55,7 @@ class TidepoolPlugin @Inject constructor( private val rateLimit: RateLimit, private val receiverDelegate: ReceiverDelegate, private val uiInteraction: UiInteraction -) : Sync, PluginBase( +) : Sync, Tidepool, PluginBase( PluginDescription() .mainType(PluginType.SYNC) .pluginName(R.string.tidepool) @@ -99,7 +101,11 @@ class TidepoolPlugin @Inject constructor( disposable += rxBus .toObservable(EventTidepoolStatus::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ event -> addToLog(event) }, fabricPrivacy::logException) + .subscribe({ event -> + addToLog(event) + // Pass to setup wizard + rxBus.send(EventSWSyncStatus(event.status)) + }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventNewBG::class.java) .observeOn(aapsSchedulers.io) diff --git a/plugins/sync/src/main/res/values/strings.xml b/plugins/sync/src/main/res/values/strings.xml index 5ab8ee3488..72f0a1e164 100644 --- a/plugins/sync/src/main/res/values/strings.xml +++ b/plugins/sync/src/main/res/values/strings.xml @@ -105,6 +105,8 @@ Urgent stale data threshold [min] Log app start to NS Copy NS settings (if exists)? + Connect to websockets + Enabling means: faster updates, receiving alarms and announcements and higher battery consumption similar to v1. All other uploaders to NS must use v3 protocol. tidepool_username @@ -177,8 +179,7 @@ - Data Broadcaster - Connect to websockets - Enabling means: faster updates, receiving alarms and announcements and higher battery consumption similar to v1. All other uploaderds to NS must use v3 protocol. + Data Broadcaster + Broadcast data to other apps like Garmin watch \ No newline at end of file