diff --git a/README.md b/README.md index b76376b3d2..f762852a96 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AndroidAPS -* Check the wiki: http://wiki.androidaps.org +* Check the wiki: https://androidaps.readthedocs.io * Everyone who’s been looping with AndroidAPS needs to fill out the form after 3 days of looping https://docs.google.com/forms/d/14KcMjlINPMJHVt28MDRupa4sz4DDIooI4SrW0P3HSN8/viewform?c=0&w=1 [![Gitter](https://badges.gitter.im/MilosKozak/AndroidAPS.svg)](https://gitter.im/MilosKozak/AndroidAPS?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) @@ -11,6 +11,4 @@ dev: [![codecov](https://codecov.io/gh/MilosKozak/AndroidAPS/branch/dev/graph/badge.svg)](https://codecov.io/gh/MilosKozak/AndroidAPS) -[![Donate via PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y4LHGJJESAVB8) - ![BTC](https://bitit.io/assets/coins/icon-btc-1e5a37bc0eb730ac83130d7aa859052bd4b53ac3f86f99966627801f7b0410be.svg) 3KawK8aQe48478s6fxJ8Ms6VTWkwjgr9f2 diff --git a/app/build.gradle b/app/build.gradle index 7a4b63ecff..fcb0b3b6c7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -259,6 +259,7 @@ dependencies { implementation 'androidx.percentlayout:percentlayout:1.0.0' implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.activity:activity-ktx:${activityVersion}" + implementation "androidx.fragment:fragment:${fragmentVersion}" implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'com.google.android.material:material:1.1.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea16c395da..8f3db8e179 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -68,7 +68,8 @@ - + diff --git a/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt index 786b30f959..749913c9a1 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt @@ -1,13 +1,29 @@ package info.nightscout.androidaps.activities +import android.content.res.ColorStateList import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.Menu +import android.widget.PopupMenu import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.Profile import info.nightscout.androidaps.data.defaultProfile.DefaultProfile +import info.nightscout.androidaps.data.defaultProfile.DefaultProfileDPV +import info.nightscout.androidaps.db.ProfileSwitch import info.nightscout.androidaps.dialogs.ProfileViewerDialog +import info.nightscout.androidaps.interfaces.ActivePluginProvider +import info.nightscout.androidaps.interfaces.DatabaseHelperInterface import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.extensions.toVisibility import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.stats.TddCalculator import kotlinx.android.synthetic.main.activity_profilehelper.* @@ -20,47 +36,253 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { @Inject lateinit var tddCalculator: TddCalculator @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var defaultProfile: DefaultProfile + @Inject lateinit var defaultProfileDPV: DefaultProfileDPV + @Inject lateinit var localProfilePlugin: LocalProfilePlugin + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var activePlugin: ActivePluginProvider + @Inject lateinit var databaseHelper: DatabaseHelperInterface + + enum class ProfileType { + MOTOL_DEFAULT, + DPV_DEFAULT, + CURRENT, + AVAILABLE_PROFILE, + PROFILE_SWITCH + } + + private var tabSelected = 0 + private val typeSelected = arrayOf(ProfileType.MOTOL_DEFAULT, ProfileType.CURRENT) + + private val ageUsed = arrayOf(15.0, 15.0) + private val weightUsed = arrayOf(0.0, 0.0) + private val tddUsed = arrayOf(0.0, 0.0) + private val pctUsed = arrayOf(32.0, 32.0) + + private lateinit var profileList: ArrayList + private val profileUsed = arrayOf(0, 0) + + private lateinit var profileSwitch: List + private val profileSwitchUsed = arrayOf(0, 0) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_profilehelper) - profilehelper_age.setParams(15.0, 1.0, 80.0, 1.0, DecimalFormat("0"), false, null) - profilehelper_weight.setParams(50.0, 5.0, 150.0, 1.0, DecimalFormat("0"), false, null) - profilehelper_tdd.setParams(50.0, 3.0, 200.0, 1.0, DecimalFormat("0"), false, null) + profilehelper_menu1.setOnClickListener { + switchTab(0, typeSelected[0]) + } + profilehelper_menu2.setOnClickListener { + switchTab(1, typeSelected[1]) + } - profilehelper_tdds.text = tddCalculator.stats() - - profilehelper_profile.setOnClickListener { - val age = profilehelper_age.value - val weight = profilehelper_weight.value - val tdd = profilehelper_tdd.value - if (age < 1 || age > 120) { - ToastUtils.showToastInUiThread(this, R.string.invalidage) - return@setOnClickListener - } - if ((weight < 5 || weight > 150) && tdd == 0.0) { - ToastUtils.showToastInUiThread(this, R.string.invalidweight) - return@setOnClickListener - } - if ((tdd < 5 || tdd > 150) && weight == 0.0) { - ToastUtils.showToastInUiThread(this, R.string.invalidweight) - return@setOnClickListener - } - profileFunction.getProfile()?.let { runningProfile -> - val profile = defaultProfile.profile(age, tdd, weight, profileFunction.getUnits()) - ProfileViewerDialog().also { pvd -> - pvd.arguments = Bundle().also { - it.putLong("time", DateUtil.now()) - it.putInt("mode", ProfileViewerDialog.Mode.PROFILE_COMPARE.ordinal) - it.putString("customProfile", runningProfile.data.toString()) - it.putString("customProfile2", profile.data.toString()) - it.putString("customProfileUnits", profile.units) - it.putString("customProfileName", "Age: $age TDD: $tdd Weight: $weight") + profilehelper_profiletype.setOnClickListener { + PopupMenu(this, profilehelper_profiletype).apply { + menuInflater.inflate(R.menu.menu_profilehelper, menu) + setOnMenuItemClickListener { item -> + profilehelper_profiletype.setText(item.title) + when (item.itemId) { + R.id.menu_default -> switchTab(tabSelected, ProfileType.MOTOL_DEFAULT) + R.id.menu_default_dpv -> switchTab(tabSelected, ProfileType.DPV_DEFAULT) + R.id.menu_current -> switchTab(tabSelected, ProfileType.CURRENT) + R.id.menu_available -> switchTab(tabSelected, ProfileType.AVAILABLE_PROFILE) + R.id.menu_profileswitch -> switchTab(tabSelected, ProfileType.PROFILE_SWITCH) } - }.show(supportFragmentManager, "ProfileViewDialog") + true + } + show() } } + // Active profile + profileList = activePlugin.activeProfileInterface.profile?.getProfileList() ?: ArrayList() + + profilehelper_available_profile_list.setOnClickListener { + PopupMenu(this, profilehelper_available_profile_list).apply { + var order = 0 + for (name in profileList) menu.add(Menu.NONE, order, order++, name) + setOnMenuItemClickListener { item -> + profilehelper_available_profile_list.setText(item.title) + profileUsed[tabSelected] = item.itemId + true + } + show() + } + } + + // Profile switch + profileSwitch = databaseHelper.getProfileSwitchData(dateUtil._now() - T.months(2).msecs(), true) + + profilehelper_profileswitch_list.setOnClickListener { + PopupMenu(this, profilehelper_profileswitch_list).apply { + var order = 0 + for (name in profileSwitch) menu.add(Menu.NONE, order, order++, name.customizedName) + setOnMenuItemClickListener { item -> + profilehelper_profileswitch_list.setText(item.title) + profileSwitchUsed[tabSelected] = item.itemId + true + } + show() + } + } + + // Default profile + profilehelper_copytolocalprofile.setOnClickListener { + val age = ageUsed[tabSelected] + val weight = weightUsed[tabSelected] + val tdd = tddUsed[tabSelected] + val pct = pctUsed[tabSelected] + val profile = if (typeSelected[tabSelected] == ProfileType.MOTOL_DEFAULT) defaultProfile.profile(age, tdd, weight, profileFunction.getUnits()) + else defaultProfileDPV.profile(age, tdd, pct / 100.0, profileFunction.getUnits()) + profile?.let { + OKDialog.showConfirmation(this, resourceHelper.gs(R.string.careportal_profileswitch), resourceHelper.gs(R.string.copytolocalprofile), Runnable { + localProfilePlugin.addProfile(LocalProfilePlugin.SingleProfile().copyFrom(localProfilePlugin.rawProfile, it, "DefaultProfile" + dateUtil.dateAndTimeAndSecondsString(dateUtil._now()))) + rxBus.send(EventLocalProfileChanged()) + }) + } + } + + profilehelper_age.setParams(0.0, 1.0, 18.0, 1.0, DecimalFormat("0"), false, null) + profilehelper_weight.setParams(0.0, 0.0, 150.0, 1.0, DecimalFormat("0"), false, null, object : TextWatcher { + override fun afterTextChanged(s: Editable) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + profilehelper_tdd_row.visibility = (profilehelper_weight.value == 0.0).toVisibility() + } + }) + profilehelper_tdd.setParams(0.0, 0.0, 200.0, 1.0, DecimalFormat("0"), false, null, object : TextWatcher { + override fun afterTextChanged(s: Editable) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + profilehelper_weight_row.visibility = (profilehelper_tdd.value == 0.0).toVisibility() + } + }) + + profilehelper_basalpctfromtdd.setParams(32.0, 32.0, 37.0, 1.0, DecimalFormat("0"), false, null) + + profilehelper_tdds.text = tddCalculator.stats() + + // Current profile + profilehelper_current_profile_text.text = profileFunction.getProfileName() + + // General + profilehelper_compareprofile.setOnClickListener { + storeValues() + for (i in 0..1) { + if (typeSelected[i] == ProfileType.MOTOL_DEFAULT) { + if (ageUsed[i] < 1 || ageUsed[i] > 18) { + ToastUtils.showToastInUiThread(this, R.string.invalidage) + return@setOnClickListener + } + if ((weightUsed[i] < 5 || weightUsed[i] > 150) && tddUsed[i] == 0.0) { + ToastUtils.showToastInUiThread(this, R.string.invalidweight) + return@setOnClickListener + } + if ((tddUsed[i] < 5 || tddUsed[i] > 150) && weightUsed[i] == 0.0) { + ToastUtils.showToastInUiThread(this, R.string.invalidweight) + return@setOnClickListener + } + } + if (typeSelected[i] == ProfileType.DPV_DEFAULT) { + if (ageUsed[i] < 1 || ageUsed[i] > 18) { + ToastUtils.showToastInUiThread(this, R.string.invalidage) + return@setOnClickListener + } + if (tddUsed[i] < 5 || tddUsed[i] > 150) { + ToastUtils.showToastInUiThread(this, R.string.invalidweight) + return@setOnClickListener + } + if ((pctUsed[i] < 32 || pctUsed[i] > 37)) { + ToastUtils.showToastInUiThread(this, R.string.invalidpct) + return@setOnClickListener + } + } + } + + getProfile(ageUsed[0], tddUsed[0], weightUsed[0], pctUsed[0] / 100.0, 0)?.let { profile0 -> + getProfile(ageUsed[1], tddUsed[1], weightUsed[1], pctUsed[1] / 100.0, 1)?.let { profile1 -> + ProfileViewerDialog().also { pvd -> + pvd.arguments = Bundle().also { + it.putLong("time", DateUtil.now()) + it.putInt("mode", ProfileViewerDialog.Mode.PROFILE_COMPARE.ordinal) + it.putString("customProfile", profile0.data.toString()) + it.putString("customProfile2", profile1.data.toString()) + it.putString("customProfileUnits", profileFunction.getUnits()) + it.putString("customProfileName", getProfileName(ageUsed[0], tddUsed[0], weightUsed[0], pctUsed[0] / 100.0, 0) + "\n" + getProfileName(ageUsed[1], tddUsed[1], weightUsed[1], pctUsed[1] / 100.0, 1)) + } + }.show(supportFragmentManager, "ProfileViewDialog") + return@setOnClickListener + } + } + ToastUtils.showToastInUiThread(this, R.string.invalidinput) + } + + switchTab(0, typeSelected[0], false) + } + + private fun getProfile(age: Double, tdd: Double, weight: Double, basalPct: Double, tab: Int): Profile? = + when (typeSelected[tab]) { + ProfileType.MOTOL_DEFAULT -> defaultProfile.profile(age, tdd, weight, profileFunction.getUnits()) + ProfileType.DPV_DEFAULT -> defaultProfileDPV.profile(age, tdd, basalPct, profileFunction.getUnits()) + ProfileType.CURRENT -> profileFunction.getProfile()?.convertToNonCustomizedProfile() + ProfileType.AVAILABLE_PROFILE -> activePlugin.activeProfileInterface.profile?.getSpecificProfile(profileList[profileUsed[tab]].toString()) + ProfileType.PROFILE_SWITCH -> profileSwitch[profileSwitchUsed[tab]].profileObject?.convertToNonCustomizedProfile() + } + + private fun getProfileName(age: Double, tdd: Double, weight: Double, basalSumPct: Double, tab: Int): String = + when (typeSelected[tab]) { + ProfileType.MOTOL_DEFAULT -> if (tdd > 0) resourceHelper.gs(R.string.formatwithtdd, age, tdd) else resourceHelper.gs(R.string.formatwithweight, age, weight) + ProfileType.DPV_DEFAULT -> resourceHelper.gs(R.string.formatwittddandpct, age, tdd, (basalSumPct * 100).toInt()) + ProfileType.CURRENT -> profileFunction.getProfileName() + ProfileType.AVAILABLE_PROFILE -> profileList[profileUsed[tab]].toString() + ProfileType.PROFILE_SWITCH -> profileSwitch[profileSwitchUsed[tab]].customizedName + } + + private fun storeValues() { + ageUsed[tabSelected] = profilehelper_age.value + weightUsed[tabSelected] = profilehelper_weight.value + tddUsed[tabSelected] = profilehelper_tdd.value + pctUsed[tabSelected] = profilehelper_basalpctfromtdd.value + } + + private fun switchTab(tab: Int, newContent: ProfileType, storeOld: Boolean = true) { + setBackgroundColorOnSelected(tab) + // Store values for selected tab. listBox values are stored on selection change + if (storeOld) storeValues() + + tabSelected = tab + typeSelected[tabSelected] = newContent + profilehelper_profiletype_title.setDefaultHintTextColor(ColorStateList.valueOf(resourceHelper.gc(if (tab == 0) R.color.tabBgColorSelected else R.color.examinedProfile))) + + // show new content + profilehelper_profiletype.setText( + when (typeSelected[tabSelected]) { + ProfileType.MOTOL_DEFAULT -> resourceHelper.gs(R.string.motoldefaultprofile) + ProfileType.DPV_DEFAULT -> resourceHelper.gs(R.string.dpvdefaultprofile) + ProfileType.CURRENT -> resourceHelper.gs(R.string.currentprofile) + ProfileType.AVAILABLE_PROFILE -> resourceHelper.gs(R.string.availableprofile) + ProfileType.PROFILE_SWITCH -> resourceHelper.gs(R.string.careportal_profileswitch) + }) + profilehelper_default_profile.visibility = (newContent == ProfileType.MOTOL_DEFAULT || newContent == ProfileType.DPV_DEFAULT).toVisibility() + profilehelper_current_profile.visibility = (newContent == ProfileType.CURRENT).toVisibility() + profilehelper_available_profile.visibility = (newContent == ProfileType.AVAILABLE_PROFILE).toVisibility() + profilehelper_profile_switch.visibility = (newContent == ProfileType.PROFILE_SWITCH).toVisibility() + + // restore selected values + profilehelper_age.value = ageUsed[tabSelected] + profilehelper_weight.value = weightUsed[tabSelected] + profilehelper_tdd.value = tddUsed[tabSelected] + profilehelper_basalpctfromtdd.value = pctUsed[tabSelected] + + profilehelper_basalpctfromtdd_row.visibility = (newContent == ProfileType.DPV_DEFAULT).toVisibility() + if (profileList.isNotEmpty()) + profilehelper_available_profile_list.setText(profileList[profileUsed[tabSelected]].toString()) + if (profileSwitch.isNotEmpty()) + profilehelper_profileswitch_list.setText(profileSwitch[profileSwitchUsed[tabSelected]].customizedName) + } + + private fun setBackgroundColorOnSelected(tab: Int) { + profilehelper_menu1.setBackgroundColor(resourceHelper.gc(if (tab == 1) R.color.defaultbackground else R.color.tabBgColorSelected)) + profilehelper_menu2.setBackgroundColor(resourceHelper.gc(if (tab == 0) R.color.defaultbackground else R.color.examinedProfile)) } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt index 2ca326d4a3..1d03ddb344 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt @@ -63,17 +63,18 @@ class SurveyActivity : NoSplashAppCompatActivity() { return@setOnClickListener } profileFunction.getProfile()?.let { runningProfile -> - val profile = defaultProfile.profile(age, tdd, weight, profileFunction.getUnits()) - ProfileViewerDialog().also { pvd -> - pvd.arguments = Bundle().also { - it.putLong("time", DateUtil.now()) - it.putInt("mode", ProfileViewerDialog.Mode.PROFILE_COMPARE.ordinal) - it.putString("customProfile", runningProfile.data.toString()) - it.putString("customProfile2", profile.data.toString()) - it.putString("customProfileUnits", profile.units) - it.putString("customProfileName", "Age: $age TDD: $tdd Weight: $weight") - } - }.show(supportFragmentManager, "ProfileViewDialog") + defaultProfile.profile(age, tdd, weight, profileFunction.getUnits())?.let { profile -> + ProfileViewerDialog().also { pvd -> + pvd.arguments = Bundle().also { + it.putLong("time", DateUtil.now()) + it.putInt("mode", ProfileViewerDialog.Mode.PROFILE_COMPARE.ordinal) + it.putString("customProfile", runningProfile.data.toString()) + it.putString("customProfile2", profile.data.toString()) + it.putString("customProfileUnits", profile.units) + it.putString("customProfileName", "Age: $age TDD: $tdd Weight: $weight") + } + }.show(supportFragmentManager, "ProfileViewDialog") + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index 4d4c52df43..7ba9957ed8 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -97,6 +97,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final ScheduledExecutorService bgWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledBgPost = null; + private static final ScheduledExecutorService bgHistoryWorker = Executors.newSingleThreadScheduledExecutor(); + private static ScheduledFuture scheduledBgHistoryPost = null; + private static long oldestBgHistoryChange = 0; + private static final ScheduledExecutorService tempBasalsWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledTemBasalsPost = null; @@ -387,8 +391,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { old.copyFrom(bgReading); getDaoBgReadings().update(old); aapsLogger.debug(LTag.DATABASE, "BG: Updating record from: " + from + " New data: " + old.toString()); - rxBus.send(new EventNewHistoryBgData(old.date)); // trigger cache invalidation - scheduleBgChange(bgReading); // trigger new calculation + scheduleBgHistoryChange(old.date); // trigger cache invalidation return false; } } catch (SQLException e) { @@ -424,6 +427,26 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } + private void scheduleBgHistoryChange(@Nullable final long timestamp) { + class PostRunnable implements Runnable { + public void run() { + aapsLogger.debug(LTag.DATABASE, "Firing EventNewBg"); + rxBus.send(new EventNewHistoryBgData(oldestBgHistoryChange)); + scheduledBgHistoryPost = null; + oldestBgHistoryChange = 0; + } + } + // prepare task for execution in 1 sec + // cancel waiting task to prevent sending multiple posts + if (scheduledBgHistoryPost != null) + scheduledBgHistoryPost.cancel(false); + Runnable task = new PostRunnable(); + final int sec = 3; + if (oldestBgHistoryChange == 0 || oldestBgHistoryChange > timestamp) oldestBgHistoryChange = timestamp; + scheduledBgHistoryPost = bgHistoryWorker.schedule(task, sec, TimeUnit.SECONDS); + + } + public List getBgreadingsDataFromTime(long mills, boolean ascending) { try { Dao daoBgreadings = getDaoBgReadings(); diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt index f179580a55..c7033eec2d 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt @@ -190,7 +190,7 @@ abstract class PluginsModule { abstract fun bindLocalProfilePlugin(plugin: LocalProfilePlugin): PluginBase @Binds - @AllConfigs + @APS @IntoMap @IntKey(250) abstract fun bindAutomationPlugin(plugin: AutomationPlugin): PluginBase diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt index f06f563874..b5b3a8f252 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt @@ -9,6 +9,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.google.common.base.Joiner +import info.nightscout.androidaps.Config import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity @@ -45,6 +46,7 @@ class InsulinDialog : DialogFragmentWithDate() { @Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var activePlugin: ActivePluginProvider @Inject lateinit var ctx: Context + @Inject lateinit var config: Config companion object { private const val PLUS1_DEFAULT = 0.5 @@ -88,6 +90,10 @@ class InsulinDialog : DialogFragmentWithDate() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + if (config.NSCLIENT) { + overview_insulin_record_only.isChecked = true + overview_insulin_record_only.isEnabled = false + } val maxInsulin = constraintChecker.getMaxBolusAllowed().value() overview_insulin_time.setParams(savedInstanceState?.getDouble("overview_insulin_time") diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt index c5f4d1ec53..84663a1cfe 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt @@ -9,6 +9,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.google.common.base.Joiner +import info.nightscout.androidaps.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.data.DetailedBolusInfo @@ -25,6 +26,7 @@ import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.SafeParse import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.resources.ResourceHelper +import kotlinx.android.synthetic.main.dialog_insulin.* import kotlinx.android.synthetic.main.dialog_treatment.* import kotlinx.android.synthetic.main.okcancel.* import java.text.DecimalFormat @@ -38,6 +40,7 @@ class TreatmentDialog : DialogFragmentWithDate() { @Inject lateinit var activePlugin: ActivePluginProvider @Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var ctx: Context + @Inject lateinit var config: Config private val textWatcher: TextWatcher = object : TextWatcher { override fun afterTextChanged(s: Editable) {} @@ -75,6 +78,10 @@ class TreatmentDialog : DialogFragmentWithDate() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + if (config.NSCLIENT) { + overview_treatment_record_only.isChecked = true + overview_treatment_record_only.isEnabled = false + } val maxCarbs = constraintChecker.getMaxCarbsAllowed().value().toDouble() val maxInsulin = constraintChecker.getMaxBolusAllowed().value() val pumpDescription = activePlugin.activePump.pumpDescription diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt index 23ddfcabd1..faf27af7b7 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt @@ -261,7 +261,7 @@ class WizardDialog : DaggerDialogFragment() { private fun calculateInsulin() { val profileStore = activePlugin.activeProfileInterface.profile - if (treatments_wizard_profile.selectedItem == null || profileStore == null) + if (treatments_wizard_profile?.selectedItem == null || profileStore == null) return // not initialized yet var profileName = treatments_wizard_profile.selectedItem.toString() val specificProfile: Profile? diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt index b67d270c8d..dff373058e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Build import android.os.Handler import android.os.HandlerThread +import android.os.SystemClock import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.androidaps.events.EventBTChange @@ -211,6 +212,7 @@ class AutomationPlugin @Inject constructor( } }) } + SystemClock.sleep(1100) event.lastRun = DateUtil.now() } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.kt index a9f3c936d9..da1ebd4914 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.kt @@ -21,8 +21,8 @@ class TriggerLocation(injector: HasAndroidInjector) : Trigger(injector) { var lastMode = InputLocationMode.Mode.INSIDE private val buttonAction = Runnable { locationDataContainer.lastLocation?.let { - latitude.value = it.latitude - longitude.value = it.longitude + latitude.setValue(it.latitude) + longitude.setValue(it.longitude) aapsLogger.debug(LTag.AUTOMATION, String.format("Grabbed location: %f %f", latitude.value, longitude.value)) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt index 565b33b77b..d90de7a086 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt @@ -209,18 +209,24 @@ class ImportExportPrefs @Inject constructor( } catch (e: IOException) { ToastUtils.errorToast(activity, e.message) log.error(TAG, "Unhandled exception", e) + } catch (e: PrefFileNotFoundError) { + ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled) + + "\n\n" + resourceHelper.gs(R.string.filenotfound) + + ": " + e.message + + "\n\n" + resourceHelper.gs(R.string.needstoragepermission)) + log.error(TAG, "File system exception", e) + } catch (e: PrefIOError) { + ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled) + + "\n\n" + resourceHelper.gs(R.string.needstoragepermission) + + ": " + e.message) + log.error(TAG, "File system exception", e) } } } fun importSharedPreferences(fragment: Fragment) { fragment.activity?.let { fragmentAct -> - val callForPrefFile = fragmentAct.registerForActivityResult(PrefsFileContract()) { - it?.let { - importSharedPreferences(fragmentAct, it) - } - } - callForPrefFile.launch(null) + importSharedPreferences(fragmentAct) } } @@ -230,7 +236,15 @@ class ImportExportPrefs @Inject constructor( importSharedPreferences(activity, it) } } - callForPrefFile.launch(null) + + try { + callForPrefFile.launch(null) + } catch (e: IllegalArgumentException) { + // this exception happens on some early implementations of ActivityResult contracts + // when registered and called for the second time + ToastUtils.errorToast(activity, resourceHelper.gs(R.string.goto_main_try_again)) + log.error(TAG, "Internal android framework exception", e) + } } private fun importSharedPreferences(activity: Activity, importFile: PrefsFile) { @@ -267,7 +281,7 @@ class ImportExportPrefs @Inject constructor( restartAppAfterImport(activity) } else { // for impossible imports it should not be called - ToastUtils.errorToast(activity, "Cannot import preferences!") + ToastUtils.errorToast(activity, resourceHelper.gs(R.string.preferences_import_impossible)) } }) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt index 9377b58992..86b1e71db6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt @@ -63,31 +63,31 @@ class MaintenancePlugin @Inject constructor( } //todo replace this with a call on startup of the application, specifically to remove -// unnecessary garbage from the log exports + // unnecessary garbage from the log exports fun deleteLogs() { - val logDirectory = LoggerUtils.getLogDirectory() - val logDir = File(logDirectory) - val files = logDir.listFiles { _: File?, name: String -> - (name.startsWith("AndroidAPS") - && name.endsWith(".zip")) - } - Arrays.sort(files) { f1: File, f2: File -> f1.name.compareTo(f2.name) } - var delFiles = listOf(*files) - val amount = sp.getInt(R.string.key_logshipper_amount, 2) - val keepIndex = amount - 1 - if (keepIndex < delFiles.size) { - delFiles = delFiles.subList(keepIndex, delFiles.size) - for (file in delFiles) { - file.delete() + LoggerUtils.getLogDirectory()?.let { logDirectory -> + val logDir = File(logDirectory) + val files = logDir.listFiles { _: File?, name: String -> + (name.startsWith("AndroidAPS") && name.endsWith(".zip")) } - } - val exportDir = File(logDirectory, "exports") - if (exportDir.exists()) { - val expFiles = exportDir.listFiles() - for (file in expFiles) { - file.delete() + Arrays.sort(files) { f1: File, f2: File -> f1.name.compareTo(f2.name) } + var delFiles = listOf(*files) + val amount = sp.getInt(R.string.key_logshipper_amount, 2) + val keepIndex = amount - 1 + if (keepIndex < delFiles.size) { + delFiles = delFiles.subList(keepIndex, delFiles.size) + for (file in delFiles) { + file.delete() + } + } + val exportDir = File(logDirectory, "exports") + if (exportDir.exists()) { + val expFiles = exportDir.listFiles() + for (file in expFiles) { + file.delete() + } + exportDir.delete() } - exportDir.delete() } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt index 40318d0ca0..240a396d92 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt @@ -656,7 +656,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList // If the target is not the same as set in the profile then oref has overridden it val targetUsed = lastRun?.constraintsProcessed?.targetBG ?: 0.0 - if (targetUsed != 0.0 && abs(profile.targetMgdl-targetUsed) > 0.01) { + if (targetUsed != 0.0 && abs(profile.targetMgdl - targetUsed) > 0.01) { aapsLogger.debug("Adjusted target. Profile: ${profile.targetMgdl} APS: $targetUsed") overview_temptarget?.text = Profile.toTargetRangeString(targetUsed, targetUsed, Constants.MGDL, units) overview_temptarget?.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) @@ -683,10 +683,10 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList overview_basebasal?.setTextColor(activeTemp?.let { resourceHelper.gc(R.color.basal) } ?: resourceHelper.gc(R.color.defaulttextcolor)) - if (activeTemp != null) - overview_basebasal_icon?.setImageResource(if (activeTemp.tempBasalConvertedToPercent(System.currentTimeMillis(), profile) > 100) R.drawable.ic_cp_basal_tbr_high else R.drawable.ic_cp_basal_tbr_low) - else - overview_basebasal_icon?.setImageResource(R.drawable.ic_cp_basal_no_tbr) + overview_basebasal_icon?.setImageResource(R.drawable.ic_cp_basal_no_tbr) + val percentRate = activeTemp?.tempBasalConvertedToPercent(System.currentTimeMillis(), profile) ?:100 + if (percentRate > 100) overview_basebasal_icon?.setImageResource(R.drawable.ic_cp_basal_tbr_high) + if (percentRate < 100) overview_basebasal_icon?.setImageResource(R.drawable.ic_cp_basal_tbr_low) // Extended bolus val extendedBolus = treatmentsPlugin.getExtendedBolusFromHistory(System.currentTimeMillis()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt index 219b56d025..668e7c7fc2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt @@ -1,6 +1,9 @@ package info.nightscout.androidaps.plugins.general.overview +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.interfaces.PluginBase @@ -26,7 +29,8 @@ class OverviewPlugin @Inject constructor( private val fabricPrivacy: FabricPrivacy, private val rxBus: RxBusWrapper, aapsLogger: AAPSLogger, - resourceHelper: ResourceHelper + resourceHelper: ResourceHelper, + private val config: Config ) : PluginBase(PluginDescription() .mainType(PluginType.GENERAL) .fragmentClass(OverviewFragment::class.qualifiedName) @@ -64,4 +68,18 @@ class OverviewPlugin @Inject constructor( disposable.clear() super.onStop() } + + override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) { + super.preprocessPreferences(preferenceFragment) + if (config.NSCLIENT) { + (preferenceFragment.findPreference(resourceHelper.gs(R.string.key_show_cgm_button)) as SwitchPreference?)?.let { + it.isVisible = false + it.isEnabled = false + } + (preferenceFragment.findPreference(resourceHelper.gs(R.string.key_show_calibration_button)) as SwitchPreference?)?.let { + it.isVisible = false + it.isEnabled = false + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationUserMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationUserMessage.kt index 3d198dfc0b..43a2094ef0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationUserMessage.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationUserMessage.kt @@ -6,6 +6,7 @@ class NotificationUserMessage (text :String): Notification() { var hash = text.hashCode() if (hash < USERMESSAGE) hash += USERMESSAGE id = hash + date = System.currentTimeMillis() this.text = text level = URGENT } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt index 50a2db2f66..9e826156ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt @@ -20,13 +20,10 @@ abstract class AbstractSensitivityPlugin( pluginDescription: PluginDescription, injector: HasAndroidInjector, aapsLogger: AAPSLogger, - resourceHelper: - ResourceHelper //, - //var sp: SP + resourceHelper: ResourceHelper, + val sp: SP ) : PluginBase(pluginDescription, aapsLogger, resourceHelper, injector), SensitivityInterface { - lateinit var sp: SP - abstract override fun detectSensitivity(plugin: IobCobCalculatorInterface, fromTime: Long, toTime: Long): AutosensResult fun fillResult(ratio: Double, carbsAbsorbed: Double, pastSensitivity: String, @@ -67,4 +64,4 @@ abstract class AbstractSensitivityPlugin( output.sensResult = sensResult return output } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java index fc527bab94..9f81d9306e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java @@ -36,7 +36,6 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP; @Singleton public class SensitivityAAPSPlugin extends AbstractSensitivityPlugin { - private SP sp; private ProfileFunction profileFunction; private DateUtil dateUtil; @@ -57,7 +56,6 @@ public class SensitivityAAPSPlugin extends AbstractSensitivityPlugin { .description(R.string.description_sensitivity_aaps), injector, aapsLogger, resourceHelper, sp ); - this.sp = sp; this.profileFunction = profileFunction; this.dateUtil = dateUtil; } @@ -66,12 +64,12 @@ public class SensitivityAAPSPlugin extends AbstractSensitivityPlugin { public AutosensResult detectSensitivity(IobCobCalculatorInterface iobCobCalculatorPlugin, long fromTime, long toTime) { LongSparseArray autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable(); - String age = sp.getString(R.string.key_age, ""); + String age = getSp().getString(R.string.key_age, ""); int defaultHours = 24; if (age.equals(getResourceHelper().gs(R.string.key_adult))) defaultHours = 24; if (age.equals(getResourceHelper().gs(R.string.key_teenage))) defaultHours = 4; if (age.equals(getResourceHelper().gs(R.string.key_child))) defaultHours = 4; - int hoursForDetection = sp.getInt(R.string.key_openapsama_autosens_period, defaultHours); + int hoursForDetection = getSp().getInt(R.string.key_openapsama_autosens_period, defaultHours); Profile profile = profileFunction.getProfile(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java index fec1b6d960..1283ef268a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java @@ -208,10 +208,10 @@ public class SensitivityOref1Plugin extends AbstractSensitivityPlugin { double basalOff = 0; if (pSensitive < 0) { // sensitive - basalOff = pSensitive * (60.0 / 5) / Profile.toMgdl(sens, profile.getUnits()); + basalOff = pSensitive * (60.0 / 5) / sens; sensResult += "Excess insulin sensitivity detected"; } else if (pResistant > 0) { // resistant - basalOff = pResistant * (60.0 / 5) / Profile.toMgdl(sens, profile.getUnits()); + basalOff = pResistant * (60.0 / 5) / sens; sensResult += "Excess insulin resistance detected"; } else { sensResult += "Sensitivity normal"; diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt index b6705ac733..bd532dc462 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt @@ -46,7 +46,7 @@ class KeepAliveReceiver : DaggerBroadcastReceiver() { companion object { private val KEEP_ALIVE_MILLISECONDS = T.mins(5).msecs() private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs() - private val IOB_UPDATE_FREQUENCY = T.mins(5).msecs() + private val IOB_UPDATE_FREQUENCY_IN_MINS = 5L private var lastReadStatus: Long = 0 private var lastRun: Long = 0 @@ -108,7 +108,7 @@ class KeepAliveReceiver : DaggerBroadcastReceiver() { else if (!loopPlugin.isEnabled() || iobCobCalculatorPlugin.actualBg() == null) shouldUploadStatus = true else if (DateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true - if (DateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY) && shouldUploadStatus) { + if (DateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINS) && shouldUploadStatus) { lastIobUpload = DateUtil.now() nsUpload.uploadDeviceStatus(loopPlugin, iobCobCalculatorPlugin, profileFunction, activePlugin.activePump, receiverStatusStore, BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION) } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt index fffa175e07..2b0fb46022 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.text.Spanned import com.google.common.base.Joiner import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.data.DetailedBolusInfo @@ -49,6 +50,7 @@ class BolusWizard @Inject constructor( @Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var loopPlugin: LoopPlugin @Inject lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin + @Inject lateinit var config: Config init { injector.androidInjector().inject(this) @@ -293,6 +295,8 @@ class BolusWizard @Inject constructor( if (abs(insulinAfterConstraints - calculatedTotalInsulin) > pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) { actions.add(resourceHelper.gs(R.string.bolusconstraintappliedwarning, resourceHelper.gc(R.color.warning), calculatedTotalInsulin, insulinAfterConstraints)) } + if (config.NSCLIENT) + actions.add("" + resourceHelper.gs(R.string.bolusrecordedonly) + "") return HtmlHelper.fromHtml(Joiner.on("
").join(actions)) } diff --git a/app/src/main/res/drawable/ic_clone_48.xml b/app/src/main/res/drawable/ic_clone_48.xml new file mode 100644 index 0000000000..5156d7525e --- /dev/null +++ b/app/src/main/res/drawable/ic_clone_48.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_profilehelper.xml b/app/src/main/res/layout/activity_profilehelper.xml index 68d97b98a8..effa56b112 100644 --- a/app/src/main/res/layout/activity_profilehelper.xml +++ b/app/src/main/res/layout/activity_profilehelper.xml @@ -1,95 +1,261 @@ + tools:context=".activities.ProfileHelperActivity"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -